1 /** 2 * An implementation of promises. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module libwasm.promise; 15 16 import std.functional; 17 import std.meta : allSatisfy, AliasSeq; 18 import std.traits : CommonType; 19 import memutils.refcounted; 20 import memutils.vector; 21 import memutils.scoped; 22 import libwasm.types; 23 24 debug (no_ae_promise) 25 { 26 } 27 else 28 debug debug = ae_promise; 29 30 /** 31 A promise for a value `T` or error `E`. 32 33 Attempts to implement the Promises/A+ spec 34 (https://promisesaplus.com/), 35 with the following deviations: 36 37 - Sections 2.2.1.1-2, 2.2.7.3-4: Due to D strong typing, the only 38 applicable interpretation of "not a function" is `null`. 39 40 - Section 2.2.5: JavaScript-specific, and does not apply to D. 41 42 - Section 2.2.7.2: In D, thrown objects may only be descendants of 43 `Throwable`. By default, `Exception` objects are caught, and 44 passed to `onRejected` handlers. 45 46 - Section 2.2.7.1/3: In the case when `onFulfilled` is `null` but 47 `onRejected` is not, the returned promise may be resolved with 48 either the fulfilled value of the current promise or the return 49 value of `onRejected`. In this case, the type of the returned 50 promise value is the D common type of the two, or `void` if none. 51 52 - Section 2.3.1: Instead of rejecting the promise with a TypeError, 53 an assertion failure is thrown. 54 55 - Section 2.3.3: Not implemented. This section facilitates 56 interoperability with other implementations of JavaScript 57 promises, though it could be implemented in D using DbI to 58 support arbitrary then-able objects. 59 60 Additionally, this implementation differs from typical JavaScript 61 implementations as follows: 62 63 - `T` may be `void`. In this case, `fulfill`, and the delegate in 64 first argument of `then`, take zero arguments instead of one. 65 66 - Instead of the constructor accepting a function which accepts the 67 `fulfill` / `reject` functions, these functions are available as 68 regular methods. 69 70 - Attempts to fulfill or reject a non-pending promise cause an 71 assertion failure instead of being silently ignored. 72 (The Promises/A+ standard touches on this in section 2.3.3.3.3.) 73 74 - `catch` is called `except` (because the former is a reserved D 75 keyword). 76 77 - `finally` is called `finish` (because the former is a reserved D 78 keyword). 79 80 - In debug builds, resolved `Promise` instances check on 81 destruction that their value / error was passed on to a handler 82 (unless they have been successfully fulfilled to a `void` value). 83 Such leaks are reported to the standard error stream. 84 */ 85 final class Promise(T, E: 86 Throwable = Exception) 87 { 88 private: 89 /// Box of `T`, if it's not `void`, otherwise empty `struct`. 90 struct Box 91 { 92 static if (!is(T == void)) 93 T value; 94 } 95 96 alias A = typeof(Box.tupleof); 97 98 PromiseState state; 99 100 union 101 { 102 Box value; 103 E error; 104 } 105 106 PromiseHandler[] handlers; 107 108 private struct PromiseHandler 109 { 110 void delegate() nothrow dg; 111 bool onFulfill, onReject; 112 } 113 114 void doFulfill(A value) nothrow 115 { 116 this.state = PromiseState.fulfilled; 117 this.value.tupleof = value; 118 static if (!is(T == void)) 119 debug (ae_promise) 120 markAsUnused(); 121 foreach (ref handler; handlers) 122 if (handler.onFulfill) 123 handler.dg(); 124 handlers = null; 125 } 126 127 void doReject(E e) nothrow 128 { 129 this.state = PromiseState.rejected; 130 this.error = e; 131 debug (ae_promise) 132 markAsUnused(); 133 foreach (ref handler; handlers) 134 if (handler.onReject) 135 handler.dg(); 136 handlers = null; 137 } 138 139 /// Implements the [[Resolve]](promise, x) resolution procedure. 140 void resolve(R, T)(R delegate(T) nothrow del, scope T param) nothrow 141 { 142 Box box; 143 static if (is(R == void)) 144 del(param); 145 else 146 box.value = del(param); 147 148 fulfill(box.tupleof); 149 } 150 151 /// Implements the [[Resolve]](promise, x) resolution procedure. 152 void resolve(R)(R delegate() nothrow del) nothrow 153 { 154 Box box; 155 static if (is(R == void)) 156 del(); 157 else 158 box.value = del(); 159 160 fulfill(box.tupleof); 161 } 162 163 /// ditto 164 void resolve(Promise!(T, E) x) nothrow 165 { 166 assert(x !is this, "Attempting to resolve a promise with itself"); 167 assert(this.state == PromiseState.pending); 168 this.state = PromiseState.following; 169 x.then(&resolveFulfill, &resolveReject); 170 } 171 172 void resolveFulfill(A value) nothrow 173 { 174 assert(this.state == PromiseState.following); 175 doFulfill(value); 176 } 177 178 void resolveReject(E e) nothrow 179 { 180 assert(this.state == PromiseState.following); 181 doReject(e); 182 } 183 184 // This debug machinery tracks leaked promises, i.e. promises 185 // which have been fulfilled/rejected, but their result was never 186 // used (their .then method was never called). 187 debug (ae_promise) 188 { 189 // Global doubly linked list of promises with unused results 190 static typeof(this) unusedHead, unusedTail; 191 typeof(this) unusedPrev, unusedNext; 192 bool isUnused() 193 { 194 return unusedPrev || (unusedHead is this); 195 } 196 197 LeakedPromiseError leakedPromiseError; 198 bool resultUsed; 199 200 void markAsUnused() 201 { 202 if (resultUsed) 203 return; // An earlier `then` call has priority 204 assert(!isUnused); 205 if (unusedTail) 206 { 207 unusedPrev = unusedTail; 208 unusedTail.unusedNext = this; 209 } 210 unusedTail = this; 211 if (!unusedHead) 212 unusedHead = this; 213 } 214 215 void markAsUsed() 216 { 217 if (resultUsed) 218 return; 219 resultUsed = true; 220 if (isUnused) 221 { 222 if (unusedPrev) 223 unusedPrev.unusedNext = unusedNext; 224 else 225 unusedHead = unusedNext; 226 if (unusedNext) 227 unusedNext.unusedPrev = unusedPrev; 228 else 229 unusedTail = unusedPrev; 230 } 231 } 232 233 static ~this() 234 { 235 for (auto p = unusedHead; p; p = p.unusedNext) 236 { 237 // If these asserts fail, there is a bug in our debug machinery 238 assert(p.state != PromiseState.pending && p.state != PromiseState.following && !p 239 .resultUsed); 240 static if (is(T == void)) 241 assert(p.state != PromiseState.fulfilled); 242 243 import libwasm.bindings.Console; 244 import libwasm.types : format; 245 246 console.error(format!"Leaked %s %s\n"( 247 p.state == PromiseState.fulfilled ? "fulfilled" : "rejected", 248 typeof(this).stringof)[]); 249 if (p.state == PromiseState.rejected) 250 _d_print_throwable(p.error); 251 _d_print_throwable(p.leakedPromiseError); 252 } 253 } 254 } 255 256 public: 257 /* 258 debug (ae_promise) this() nothrow 259 { 260 // Record instantiation point 261 try 262 throw new LeakedPromiseError(); 263 catch (LeakedPromiseError e) 264 leakedPromiseError = e; 265 catch (Throwable) 266 { 267 } // allow nothrow 268 } 269 */ 270 /// A tuple of this `Promise`'s value. 271 /// Either `(T)` or an empty tuple. 272 alias ValueTuple = A; 273 274 /// Work-around for DMD bug 21804: 275 /// https://issues.dlang.org/show_bug.cgi?id=21804 276 /// If your `then` callback argument is a tuple, 277 /// insert this call before the `then` call. 278 /// (Needs to be done only once per `Promise!T` instance.) 279 typeof(this) dmd21804workaround() 280 { 281 static if (!is(T == void)) 282 if (false) 283 then((A result) {}); 284 return this; 285 } 286 287 /// Ignore this promise leaking in debug builds. 288 void ignoreResult() 289 { 290 debug (ae_promise) 291 markAsUsed(); 292 } 293 294 /// Fulfill this promise, with the given value (if applicable). 295 void fulfill(A value) nothrow 296 { 297 assert(this.state == PromiseState.pending, 298 "This promise is already fulfilled, rejected, or following another promise."); 299 doFulfill(value); 300 } 301 302 /// Reject this promise, with the given exception. 303 void reject(E e) nothrow 304 { 305 assert(this.state == PromiseState.pending, 306 "This promise is already fulfilled, rejected, or following another promise."); 307 doReject(e); 308 } 309 310 /// Registers the specified fulfillment and rejection handlers. 311 /// If the promise is already resolved, they are called 312 /// as soon as possible (but not immediately). 313 Promise!(Unpromise!R, F) then(R, F = E)(R delegate(A) nothrow onFulfilled, R delegate(E) nothrow onRejected = null) nothrow 314 { 315 static if (!is(T : R)) 316 assert(onFulfilled, "Cannot implicitly propagate " ~ T.stringof ~ " to " ~ R.stringof ~ " due to null onFulfilled"); 317 318 auto next = new typeof(return); 319 320 void fulfillHandler() nothrow 321 { 322 assert(this.state == PromiseState.fulfilled); 323 if (onFulfilled) 324 { 325 next.resolve(onFulfilled, this.value.tupleof); 326 } 327 else 328 { 329 static if (is(R == void)) 330 next.fulfill(); 331 else 332 { 333 static if (!is(T : R)) 334 assert(false); // verified above 335 else 336 next.fulfill(this.value.tupleof); 337 } 338 } 339 } 340 341 void rejectHandler() nothrow 342 { 343 assert(this.state == PromiseState.rejected); 344 if (onRejected) 345 { 346 next.resolve(onRejected, this.error); 347 } 348 else 349 next.reject(this.error); 350 } 351 352 final switch (this.state) 353 { 354 case PromiseState.pending: 355 case PromiseState.following: 356 handlers ~= PromiseHandler({ callSoon(&fulfillHandler); }, true, false); 357 handlers ~= PromiseHandler({ callSoon(&rejectHandler); }, false, true); 358 break; 359 case PromiseState.fulfilled: 360 callSoon(&fulfillHandler); 361 break; 362 case PromiseState.rejected: 363 callSoon(&rejectHandler); 364 break; 365 } 366 367 debug (ae_promise) 368 markAsUsed(); 369 return next; 370 } 371 372 /// Special overload of `then` with no `onFulfilled` function. 373 /// In this scenario, `onRejected` can act as a filter, 374 /// converting errors into values for the next promise in the chain. 375 Promise!(CommonType!(Unpromise!R, T), F) then(R, F = E)(typeof(null) onFulfilled, R delegate(E) nothrow onRejected) nothrow 376 { 377 // The returned promise will be fulfilled with either 378 // `this.value` (if `this` is fulfilled), or the return value 379 // of `onRejected` (if `this` is rejected). 380 alias C = CommonType!(Unpromise!R, T); 381 382 auto next = new typeof(return); 383 384 void fulfillHandler() /*nothrow*/ 385 { 386 assert(this.state == PromiseState.fulfilled); 387 static if (is(C == void)) 388 next.fulfill(); 389 else 390 next.fulfill(this.value.tupleof); 391 } 392 393 void rejectHandler() /*nothrow*/ 394 { 395 assert(this.state == PromiseState.rejected); 396 if (onRejected) 397 { 398 next.resolve(onRejected, this.error); 399 } 400 else 401 next.reject(this.error); 402 } 403 404 final switch (this.state) 405 { 406 case PromiseState.pending: 407 case PromiseState.following: 408 handlers ~= PromiseHandler({ callSoon(&fulfillHandler); }, true, false); 409 handlers ~= PromiseHandler({ callSoon(&rejectHandler); }, false, true); 410 break; 411 case PromiseState.fulfilled: 412 callSoon(&fulfillHandler); 413 break; 414 case PromiseState.rejected: 415 callSoon(&rejectHandler); 416 break; 417 } 418 419 debug (ae_promise) 420 markAsUsed(); 421 return next; 422 } 423 424 /// Registers a rejection handler. 425 /// Equivalent to `then(null, onRejected)`. 426 /// Similar to the `catch` method in JavaScript promises. 427 Promise!(R, F) except(R, F = E)(R delegate(E) nothrow onRejected) nothrow 428 { 429 return this.then(null, onRejected); 430 } 431 432 /// Registers a finalization handler, which is called when the 433 /// promise is resolved (either fulfilled or rejected). 434 /// Roughly equivalent to `then(value => onResolved(), error => onResolved())`. 435 /// Similar to the `finally` method in JavaScript promises. 436 Promise!(R, F) finish(R, F = E)(R delegate() nothrow onResolved) nothrow 437 { 438 assert(onResolved, "No onResolved delegate specified in .finish"); 439 440 auto next = new typeof(return); 441 442 void handler() nothrow 443 { 444 assert(this.state == PromiseState.fulfilled || this.state == PromiseState.rejected); 445 next.resolve(onResolved); 446 } 447 448 final switch (this.state) 449 { 450 case PromiseState.pending: 451 case PromiseState.following: 452 handlers ~= PromiseHandler({ callSoon(&handler); }, true, true); 453 break; 454 case PromiseState.fulfilled: 455 case PromiseState.rejected: 456 callSoon(&handler); 457 break; 458 } 459 460 debug (ae_promise) 461 markAsUsed(); 462 return next; 463 } 464 } 465 466 // (These declarations are top-level because they don't need to be templated.) 467 468 private enum PromiseState 469 { 470 pending, 471 following, 472 fulfilled, 473 rejected, 474 } 475 476 debug (ae_promise) 477 { 478 private final class LeakedPromiseError : Throwable 479 { 480 this() 481 { 482 super("Created here:"); 483 } 484 } 485 486 private extern (C) void _d_print_throwable(Throwable t) @nogc; 487 } 488 489 // The reverse operation is the `.resolve` overload. 490 private template Unpromise(P) 491 { 492 static if (is(P == Promise!(T, E), T, E)) 493 alias Unpromise = T; 494 else 495 alias Unpromise = P; 496 } 497 498 // This is the only non-"pure" part of this implementation. 499 private void callSoon()(void delegate() nothrow dg) @safe nothrow 500 { 501 setTimeout(dg, 1); 502 } 503 504 // This is just a simple instantiation test. 505 // The full test suite (D translation of the Promises/A+ conformance 506 // test) is here: https://github.com/CyberShadow/ae-promises-tests 507 nothrow unittest 508 { 509 static bool never; 510 if (never) 511 { 512 Promise!int test; 513 test.then((int i) {}); 514 test.then((int i) {}, (Exception e) {}); 515 test.then(null, (Exception e) {}); 516 test.except((Exception e) {}); 517 test.finish({}); 518 test.fulfill(1); 519 test.reject(Exception.init); 520 521 Promise!void test2; 522 test2.then({}); 523 } 524 } 525 526 // Non-Exception based errors 527 unittest 528 { 529 static bool never; 530 if (never) 531 { 532 static class OtherException : Exception 533 { 534 this() 535 { 536 super(null); 537 } 538 } 539 540 Promise!(int, OtherException) test; 541 test.then((int i) {}); 542 test.then((int i) {}, (OtherException e) {}); 543 test.then(null, (OtherException e) {}); 544 test.except((OtherException e) {}); 545 test.fulfill(1); 546 test.reject(OtherException.init); 547 } 548 } 549 550 // **************************************************************************** 551 552 /// Returns a new `Promise!void` which is resolved. 553 Promise!void resolve(E = Exception)() 554 { 555 auto p = new Promise!(void, E)(); 556 p.fulfill(); 557 return p; 558 } 559 560 /// Returns a new `Promise` which is resolved with the given value. 561 Promise!T resolve(T, E = Exception)(T value) 562 { 563 auto p = new Promise!(T, E)(); 564 p.fulfill(value); 565 return p; 566 } 567 568 /// Returns a new `Promise` which is rejected with the given reason. 569 Promise!(T, E) reject(T, E)(E reason) 570 { 571 auto p = new Promise!(T, E)(); 572 p.reject(reason); 573 return p; 574 } 575 576 // **************************************************************************** 577 578 /// Return `true` if `P` is a `Promise` instantiation. 579 template isPromise(P) 580 { 581 static if (is(P == Promise!(T, E), T, E)) 582 enum isPromise = true; 583 else 584 enum isPromise = false; 585 } 586 587 /// Get the value type of the promise `P`, 588 /// i.e. its `T` parameter. 589 template PromiseValue(P) 590 { 591 /// 592 static if (is(P == Promise!(T, E), T, E)) 593 alias PromiseValue = T; 594 else 595 static assert(false); 596 } 597 598 /// Get the error type of the promise `P`, 599 /// i.e. its `E` parameter. 600 template PromiseError(P) 601 { 602 /// 603 static if (is(P == Promise!(T, E), T, E)) 604 alias PromiseError = E; 605 else 606 static assert(false); 607 } 608 609 /// Construct a new Promise type based on `P`, 610 /// if the given transformation was applied on the value type. 611 /// If `P` is a `void` Promise, then the returned promise 612 /// will also be `void`. 613 template PromiseValueTransform(P, alias transform) if (is(P == Promise!(T, E), T, E)) 614 { 615 /// ditto 616 static if (is(P == Promise!(T, E), T, E)) 617 { 618 static if (is(T == void)) 619 private alias T2 = void; 620 else 621 private alias T2 = typeof({ T* value; return transform(*value); }()); 622 alias PromiseValueTransform = Promise!(T2, E); 623 } 624 } 625 626 // **************************************************************************** 627 628 /// Wait for all promises to be resolved, or for any to be rejected. 629 PromiseValueTransform!(P, x => [x]) all(P)(P[] promises) 630 if (is(P == Promise!(T, E), T, E)) 631 { 632 alias T = PromiseValue!P; 633 634 auto allPromise = new typeof(return); 635 636 typeof(return).ValueTuple results; 637 static if (!is(T == void)) 638 results[0] = new T[promises.length]; 639 640 if (promises.length) 641 { 642 size_t numResolved; 643 foreach (i, p; promises) 644 (i, p) { 645 p.dmd21804workaround.then((P.ValueTuple result) { 646 if (allPromise) 647 { 648 static if (!is(T == void)) 649 results[0][i] = result[0]; 650 if (++numResolved == promises.length) 651 allPromise.fulfill(results); 652 } 653 }, (error) { 654 if (allPromise) 655 { 656 allPromise.reject(error); 657 allPromise = null; // ignore successive resolves / rejects 658 } 659 }); 660 }(i, p); 661 } 662 else 663 allPromise.fulfill(results); 664 return allPromise; 665 } 666 /* 667 nothrow unittest 668 { 669 import std.exception : assertNotThrown; 670 671 int result; 672 auto p1 = new Promise!int; 673 auto p2 = new Promise!int; 674 auto p3 = new Promise!int; 675 p2.fulfill(2); 676 auto pAll = all([p1, p2, p3]); 677 p1.fulfill(1); 678 pAll.dmd21804workaround.then((values) { 679 result = values[0] + values[1] + values[2]; 680 }); 681 p3.fulfill(3); 682 socketManager.loop().assertNotThrown; 683 assert(result == 6); 684 } 685 686 nothrow unittest 687 { 688 import std.exception : assertNotThrown; 689 690 int called; 691 auto p1 = new Promise!void; 692 auto p2 = new Promise!void; 693 auto p3 = new Promise!void; 694 p2.fulfill(); 695 auto pAll = all([p1, p2, p3]); 696 p1.fulfill(); 697 pAll.then({ called = true; }); 698 socketManager.loop().assertNotThrown; 699 assert(!called); 700 p3.fulfill(); 701 socketManager.loop().assertNotThrown; 702 assert(called); 703 } 704 705 nothrow unittest 706 { 707 import std.exception : assertNotThrown; 708 709 Promise!void[] promises; 710 auto pAll = all(promises); 711 bool called; 712 pAll.then({ called = true; }); 713 socketManager.loop().assertNotThrown; 714 assert(called); 715 }*/ 716 717 private template AllResultImpl(size_t promiseIndex, size_t resultIndex, Promises...) 718 { 719 static if (Promises.length == 0) 720 { 721 alias TupleMembers = AliasSeq!(); 722 enum size_t[] mapping = []; 723 } 724 else static if (is(PromiseValue!(Promises[0]) == void)) 725 { 726 alias Next = AllResultImpl!(promiseIndex + 1, resultIndex, Promises[1 .. $]); 727 alias TupleMembers = Next.TupleMembers; 728 enum size_t[] mapping = [size_t(-1)] ~ Next.mapping; 729 } 730 else 731 { 732 alias Next = AllResultImpl!(promiseIndex + 1, resultIndex + 1, Promises[1 .. $]); 733 alias TupleMembers = AliasSeq!(PromiseValue!(Promises[0]), Next.TupleMembers); 734 enum size_t[] mapping = [resultIndex] ~ Next.mapping; 735 } 736 } 737 738 // Calculates a value type for a Promise suitable to hold the values of the given promises. 739 // void-valued promises are removed; an empty list is converted to void. 740 // Also calculates an index map from Promises indices to tuple member indices. 741 private template AllResult(Promises...) 742 { 743 alias Impl = AllResultImpl!(0, 0, Promises); 744 static if (Impl.TupleMembers.length == 0) 745 alias ResultType = void; 746 else 747 { 748 import std.typecons : Tuple; 749 750 alias ResultType = Tuple!(Impl.TupleMembers); 751 } 752 } 753 754 private alias PromiseBox(P) = P.Box; 755 756 /// Heterogeneous variant, which resolves to a tuple. 757 /// void promises' values are omitted from the result tuple. 758 /// If all promises are void, then so is the result. 759 Promise!(AllResult!Promises.ResultType) all(Promises...)(Promises promises) 760 if (allSatisfy!(isPromise, Promises)) 761 { 762 AllResult!Promises.Impl.TupleMembers results; 763 764 auto allPromise = new typeof(return); 765 766 static if (promises.length) 767 { 768 size_t numResolved; 769 foreach (i, p; promises) 770 { 771 alias P = typeof(p); 772 alias T = PromiseValue!P; 773 p.dmd21804workaround.then((P.ValueTuple result) { 774 if (allPromise) 775 { 776 static if (!is(T == void)) 777 results[AllResult!Promises.Impl.mapping[i]] = result[0]; 778 if (++numResolved == promises.length) 779 { 780 static if (AllResult!Promises.Impl.TupleMembers.length) 781 { 782 import std.typecons : tuple; 783 784 allPromise.fulfill(tuple(results)); 785 } 786 else 787 allPromise.fulfill(); 788 } 789 } 790 }, (error) { 791 if (allPromise) 792 { 793 allPromise.reject(error); 794 allPromise = null; // ignore successive resolves / rejects 795 } 796 }); 797 } 798 } 799 else 800 allPromise.fulfill(); 801 return allPromise; 802 } 803 /* 804 nothrow unittest 805 { 806 import std.exception : assertNotThrown; 807 import ae.utils.meta : I; 808 809 int result; 810 auto p1 = new Promise!byte; 811 auto p2 = new Promise!void; 812 auto p3 = new Promise!int; 813 p2.fulfill(); 814 auto pAll = all(p1, p2, p3); 815 p1.fulfill(1); 816 pAll.dmd21804workaround 817 .then(values => values.expand.I!((v1, v3) { result = v1 + v3; })); 818 p3.fulfill(3); 819 socketManager.loop().assertNotThrown; 820 assert(result == 4); 821 } 822 823 nothrow unittest 824 { 825 bool ok; 826 import std.exception : assertNotThrown; 827 828 auto p1 = new Promise!void; 829 auto p2 = new Promise!void; 830 auto p3 = new Promise!void; 831 p2.fulfill(); 832 auto pAll = all(p1, p2, p3); 833 p1.fulfill(); 834 pAll.then({ ok = true; }); 835 socketManager.loop().assertNotThrown; 836 assert(!ok); 837 p3.fulfill(); 838 socketManager.loop().assertNotThrown; 839 assert(ok); 840 } 841 */ 842 // **************************************************************************** 843 844 Promise!(T, E) require(T, E)(ref Promise!(T, E) p, lazy Promise!(T, E) lp) 845 { 846 if (!p) 847 p = lp; 848 return p; 849 } 850 851 unittest 852 { 853 Promise!int p; 854 int work; 855 Promise!int getPromise() 856 { 857 return p.require({ work++; return resolve(1); }()); 858 } 859 860 int done; 861 getPromise().then((n) { done += 1; }); 862 getPromise().then((n) { done += 1; }); 863 //socketManager.loop(); 864 //assert(work == 1 && done == 2); 865 } 866 /* 867 /// Ordered promise queue, supporting asynchronous enqueuing / fulfillment. 868 struct PromiseQueue(T, E = Exception) 869 { 870 private alias P = Promise!(T, E); 871 872 private P[] fulfilled, waiting; 873 874 import ae.utils.array : queuePush, queuePop; 875 876 /// Retrieve the next fulfilled promise, or enqueue a waiting one. 877 P waitOne() 878 { 879 if (fulfilled.length) 880 return fulfilled.queuePop(); 881 882 auto p = new P; 883 waiting.queuePush(p); 884 return p; 885 } 886 887 /// Fulfill one waiting promise, or enqueue a fulfilled one. 888 P fulfillOne(typeof(P.Box.tupleof) value) 889 { 890 if (waiting.length) 891 { 892 waiting.queuePop.fulfill(value); 893 return null; 894 } 895 896 auto p = new P; 897 p.fulfill(value); 898 fulfilled.queuePush(p); 899 return p; 900 } 901 } 902 903 unittest 904 { 905 PromiseQueue!int q; 906 q.fulfillOne(1); 907 q.fulfillOne(2); 908 int[] result; 909 q.waitOne().then((i) { result ~= i; }); 910 q.waitOne().then((i) { result ~= i; }); 911 socketManager.loop(); 912 assert(result == [1, 2]); 913 } 914 915 unittest 916 { 917 PromiseQueue!int q; 918 int[] result; 919 q.waitOne().then((i) { result ~= i; }); 920 q.waitOne().then((i) { result ~= i; }); 921 q.fulfillOne(1); 922 q.fulfillOne(2); 923 socketManager.loop(); 924 assert(result == [1, 2]); 925 } 926 */