1 /**
2  * async/await-like API for asynchronous tasks combining promises and
3  * fibers.
4  *
5  * License:
6  *   This Source Code Form is subject to the terms of
7  *   the Mozilla Public License, v. 2.0. If a copy of
8  *   the MPL was not distributed with this file, You
9  *   can obtain one at http://mozilla.org/MPL/2.0/.
10  *
11  * Authors:
12  *   Vladimir Panteleev <ae@cy.md>
13  */
14 
15 module ae.utils.promise.await;
16 
17 import core.thread : Fiber;
18 
19 import ae.utils.promise;
20 
21 enum defaultFiberSize = 64 * 1024;
22 
23 /// Evaluates `task` in a new fiber, and returns a promise which is
24 /// fulfilled when `task` exits.  `task` may use `await` to block on
25 /// other promises.
26 // TODO: is using lazy OK for this? https://issues.dlang.org/show_bug.cgi?id=23923
27 Promise!(T, E) async(T, E = Exception)(lazy T task, size_t size = defaultFiberSize)
28 if (!is(T == return))
29 {
30 	return async({ return task; }, size);
31 }
32 
33 /// ditto
34 Promise!(T, E) async(T, E = Exception)(T delegate() task, size_t size = defaultFiberSize)
35 if (!is(T == return))
36 {
37 	auto p = new Promise!T;
38 	auto f = new Fiber({
39 		try
40 			static if (is(T == void))
41 				task(), p.fulfill();
42 			else
43 				p.fulfill(task());
44 		catch (E e)
45 			p.reject(e);
46 	}, size);
47 	f.call();
48 	return p;
49 }
50 
51 /// ditto
52 Promise!(T, E) async(T, E = Exception)(T function() task)
53 if (!is(T == return))
54 {
55 	import std.functional : toDelegate;
56 	return threadAsync(task.toDelegate);
57 }
58 
59 /// Synchronously waits until the promise `p` is fulfilled.
60 /// Can only be called in a fiber.
61 T await(T, E)(Promise!(T, E) p)
62 {
63 	Promise!T.ValueTuple fiberValue;
64 	E fiberError;
65 
66 	auto f = Fiber.getThis();
67 	assert(f, "await called while not in a fiber");
68 	p.then((Promise!T.ValueTuple value) {
69 		fiberValue = value;
70 		f.call();
71 	}, (E error) {
72 		fiberError = error;
73 		f.call();
74 	});
75 	Fiber.yield();
76 	if (fiberError)
77 		throw fiberError;
78 	else
79 	{
80 		static if (!is(T == void))
81 			return fiberValue[0];
82 	}
83 }
84 
85 ///
86 unittest
87 {
88 	import ae.net.asockets : socketManager;
89 
90 	auto one = resolve(1);
91 	auto two = resolve(2);
92 
93 	int sum;
94 	async(one.await + two.await).then((value) {
95 		sum = value;
96 	});
97 	socketManager.loop();
98 	assert(sum == 3);
99 }
100 
101 unittest
102 {
103 	if (false)
104 	{
105 		async({}()).await();
106 	}
107 }