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 */