1 /+
2 JS-like promise
3 +/
4 module sily.async.promise;
5 
6 import std.net.curl: HTTPStatusException;
7 import std.concurrency;
8 import core.thread;
9 
10 /// Wrapper for Curl requests
11 alias HTTPRequest = Promise!(string, HTTPStatusException);
12 
13 private struct PromiseHandler {
14     void delegate() handler;
15     bool onFullfill;
16     bool onReject;
17 }
18 
19 /++
20 Simple implementation of JavaScript promises
21 Example:
22 ---
23 HTTPRequest prom = new Promise!(string, HTTPStatusException)();
24 
25 prom.then(delegate void(string s) {
26     writeln(s);
27 }).then(null, delegate void(HTTPStatusException e) {
28     writeln("Error ", e.status, ": ",  e.msg);
29 }).except(delegate void(HTTPStatusException e) {
30     writeln(e.msg);
31 }).finish(delegate void() {
32     writeln("Finished after error");
33 });
34 
35 prom.resolve("My data");
36 prom.refresh();
37 prom.reject(new HTTPStatusException(451, "Reject message"));
38 ---
39 +/
40 final class Promise(T, E: Throwable = Exception) {
41 
42     private PromiseHandler[] _handlers;
43 
44     private PromiseState _state;
45 
46     private alias ThisType = Promise!(T, E);
47 
48     private struct BlackBox {
49         static if (!is(T == void)) T value;
50     }
51 
52     private BlackBox _value;
53     private E _error;
54 
55     alias V = typeof(BlackBox.tupleof);
56     
57     /// Resolves promise and calles `then onResolve` callbacks
58     private void resolve(BlackBox val) {
59         _state = PromiseState.fulfilled;
60         _value = val;
61         foreach (func; _handlers) {
62             if (func.onFullfill) {
63                 func.handler();
64             }
65         }
66         _handlers = null;
67     }
68 
69     /// Ditto
70     void resolve() {
71         resolve(BlackBox());
72     }
73 
74     static if (!is(T == void)) {
75         /// Resolves promise and calles `then onResolve` callbacks
76         void resolve(T val) {
77             resolve(BlackBox(val));
78         }
79     }
80 
81     /// Resolves promise and calles `then onError` callback
82     void reject(E err) {
83         _state = PromiseState.rejected;
84         _error = err;
85         foreach (func; _handlers) {
86             if (func.onReject) {
87                 func.handler();
88             }
89         }
90         _handlers = null;
91     }
92 
93     private void tryResolve(R, S)(R delegate(S) callback, S val) {
94         static if (is(R == void)) {
95             static if (is(S == void)) {
96                 callback();
97                 resolve();
98             } else {
99                 callback(val);
100                 resolve();
101             }
102         } else {
103             static if (is(S == void)) {
104                 resolve(callback());
105             } else {
106                 resolve(callback(val));
107             }
108         }
109     }
110 
111     private void tryResolve(R)(R delegate() callback) {
112         static if (is(R == void)) {
113             callback();
114             resolve();
115         } else {
116             resolve(callback());
117         }
118     }
119     
120     /// Registers on resolve functions (set null for no callback)
121     Promise!(S, F) then(S, F = E)(S delegate(V) onResolve, S delegate(E) onReject = null) {
122         Promise!(S, F) next = new Promise!(S, F);
123 
124         void resolveHandler() {
125             if (onResolve !is null) {
126                 try {
127                     // next.resolve(onResolve(_value.tupleof));
128                     next.tryResolve(onResolve, _value.tupleof);
129                 } catch (F e) {
130                     next.reject(e);
131                 }
132             } else {
133                 next.resolve();
134                 // static if (is(S == void)) {
135                 //     next.resolve();
136                 // } else {
137                 //     next.resolve(_value.tupleof);
138                 // }
139             }
140         }
141 
142         void rejectHandler() {
143             if (onReject !is null) {
144                 try {
145                     // next.resolve(onReject(_error));
146                     next.tryResolve(onReject, _error);
147                 } catch (F e) {
148                     next.reject(e);
149                 }
150             } else {
151                 next.reject(_error);
152             }
153         }
154 
155         switch (_state) {
156             case PromiseState.pending:
157                 _handlers ~= PromiseHandler(&resolveHandler, true, false);
158                 _handlers ~= PromiseHandler(&rejectHandler, false, true);
159             break;
160             case PromiseState.fulfilled:
161                 resolveHandler();
162             break;
163             case PromiseState.rejected:
164                 rejectHandler();
165             break;
166             default: break;
167         }
168 
169         return next;
170     }
171     
172     /// Registers catch callback, equivalent to `then(null, ErrorCallback)`
173     Promise!(S, F) except(S, F = E)(S delegate(F) onCatch) {
174         return this.then(null, onCatch);
175     }
176     
177     /// Registers callback to be called at end. Equivalent to `then(onFinally, onFinally)`
178     Promise!(S, E) finish(S)(S delegate(V) onResolve) {
179         Promise!(S, E) next = new Promise!(S, E);
180 
181         void resolveHandler() {
182             if (onResolve !is null) {
183                 try {
184                     // next.resolve(onResolve(_value.tupleof));
185                     next.tryResolve(onResolve, _value.tupleof);
186                 } catch (E e) {
187                     next.reject(e);
188                 }
189             } else {
190                 static if (is(S == void)) {
191                     next.resolve();
192                 } else {
193                     next.resolve(_value.tupleof);
194                 }
195             }
196         }
197 
198         switch (_state) {
199             case PromiseState.pending:
200                 _handlers ~= PromiseHandler(&resolveHandler, true, true);
201             break;
202             case PromiseState.fulfilled:
203                 resolveHandler();
204             break;
205             case PromiseState.rejected:
206                 resolveHandler();
207             break;
208             default: break;
209         }
210 
211         return next;
212     }
213 }
214 
215 private enum PromiseState {
216     pending,
217     fulfilled,
218     rejected
219 }
220 
221