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