Skip to content

Commit 2d165f3

Browse files
committed
Merge branch 'release/v2.0.0'
2 parents e61c218 + a63edd2 commit 2d165f3

10 files changed

+640
-352
lines changed

Diff for: .imptest

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2-
"modelId": "BYqvwKEI-VsP" /* April/EnvTail.E */,
2+
"modelId": "OB5-O5idLyE1" /* Vimp.3 */,
33
"devices": [
4-
"2370797838a609ee" /* EI.Test.E @ Promise lib */
4+
"4353450000000003" /* EI.Test.Vimp.3 @ Promise lib */
55
],
6-
"agentFile": "promise.class.nut",
6+
"agentFile": "Promise.class.nut",
77
"deviceFile": false,
88
"stopOnFailure": false,
99
"timeout": 3,
1010
"tests": [
1111
"*.test.nut",
1212
"tests/**/*.test.nut"
1313
]
14-
}
14+
}

Diff for: LICENSE.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 SMS Diagnostics Pty Ltd
43
Copyright (c) 2016 Electric Imp
4+
Copyright (c) 2015 SMS Diagnostics Pty Ltd
55

66
Permission is hereby granted, free of charge, to any person obtaining a copy
77
of this software and associated documentation files (the "Software"), to deal

Diff for: Promise.class.nut

+329
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
/**
2+
* Promises for Electric Imp/Squirrel
3+
*
4+
* @author Mikhail Yurasov <mikhail@electricimp.com>
5+
* @author Aron Steg <aron@electricimp.com>
6+
*
7+
* @version 2.0.0
8+
*/
9+
class Promise {
10+
static version = [2, 0, 0];
11+
12+
static STATE_PENDING = 0;
13+
static STATE_RESOLVED = 1;
14+
static STATE_REJECTED = 2;
15+
static STATE_CANCELLED = 3;
16+
17+
_state = null;
18+
_value = null;
19+
20+
/* @var {{resole, reject}[]} _handlers */
21+
_handlers = null;
22+
23+
/**
24+
* @param {function(resolve, reject)} action - action function
25+
*/
26+
constructor(action) {
27+
this._state = this.STATE_PENDING;
28+
this._handlers = [];
29+
30+
try {
31+
action(
32+
this._resolve.bindenv(this)
33+
this._reject.bindenv(this)
34+
);
35+
} catch (e) {
36+
this._reject(e);
37+
}
38+
}
39+
40+
/**
41+
* Execute chain of handlers
42+
*/
43+
function _callHandlers() {
44+
if (this.STATE_PENDING != this._state) {
45+
foreach (handler in this._handlers) {
46+
(/* create closure and bind handler to it */ function (handler) {
47+
if (this._state == this.STATE_RESOLVED) {
48+
if ("resolve" in handler && "function" == type(handler.resolve)) {
49+
imp.wakeup(0, function() {
50+
handler.resolve(this._value);
51+
}.bindenv(this));
52+
}
53+
} else if (this._state == this.STATE_REJECTED) {
54+
if ("reject" in handler && "function" == type(handler.reject)) {
55+
imp.wakeup(0, function() {
56+
handler.reject(this._value);
57+
}.bindenv(this));
58+
}
59+
} else if (this._state == this.STATE_CANCELLED) {
60+
if ("cancel" in handler && "function" == type(handler.cancel)) {
61+
imp.wakeup(0, function() {
62+
handler.cancel(this._value);
63+
}.bindenv(this));
64+
}
65+
}
66+
})(handler);
67+
}
68+
69+
this._handlers = [];
70+
}
71+
}
72+
73+
/**
74+
* Resolve promise with a value
75+
*/
76+
function _resolve(value = null) {
77+
if (this.STATE_PENDING == this._state) {
78+
// if promise is resolved with another promise
79+
// let it resolve/reject this one,
80+
// otherwise resolve immideately
81+
if (this._isPromise(value)) {
82+
value.then(
83+
this._resolve.bindenv(this),
84+
this._reject.bindenv(this)
85+
);
86+
} else {
87+
this._state = this.STATE_RESOLVED;
88+
this._value = value;
89+
this._callHandlers();
90+
}
91+
}
92+
}
93+
94+
/**
95+
* Reject promise for a reason
96+
*/
97+
function _reject(reason = null) {
98+
if (this.STATE_PENDING == this._state) {
99+
this._state = this.STATE_REJECTED;
100+
this._value = reason;
101+
this._callHandlers();
102+
}
103+
}
104+
105+
/**
106+
* Check if a value is a Promise.
107+
* @param {Promise|*} value
108+
* @return {boolean}
109+
*/
110+
function _isPromise(value) {
111+
if (
112+
// detect that the value is some form of Promise
113+
// by the fact it has .then() method
114+
(typeof value == "instance")
115+
&& ("then" in value)
116+
&& (typeof value.then == "function")
117+
) {
118+
return true
119+
}
120+
121+
return false
122+
}
123+
124+
/**
125+
* Add handlers on resolve/rejection
126+
* @param {function} onResolve
127+
* @param {function|null} onReject
128+
* @return {this}
129+
*/
130+
function then(onResolve, onReject = null) {
131+
this._handlers.push({
132+
"resolve": onResolve
133+
});
134+
135+
if (onReject) {
136+
this._handlers.push({
137+
"reject": onReject
138+
});
139+
}
140+
141+
this._callHandlers();
142+
return this;
143+
}
144+
145+
/**
146+
* Add handler on rejection
147+
* @param {function} onReject
148+
* @return {this}
149+
*/
150+
function fail(onReject) {
151+
this._handlers.push({
152+
"reject": onReject
153+
});
154+
155+
this._callHandlers();
156+
return this;
157+
}
158+
159+
/**
160+
* Add handler that is executed both on resolve and rejection
161+
* @param {function(value)} handler
162+
* @return {this}
163+
*/
164+
function finally(handler) {
165+
this._handlers.push({
166+
"resolve": handler,
167+
"reject": handler
168+
});
169+
170+
this._callHandlers();
171+
return this;
172+
}
173+
174+
/**
175+
* Add handlers on cancellation
176+
* @param {function()} onCancel
177+
* @return {this}
178+
*/
179+
function cancelled(onCancel) {
180+
this._handlers.push({
181+
"cancel": onCancel
182+
});
183+
184+
this._callHandlers();
185+
return this;
186+
}
187+
188+
/**
189+
* Add handler that is executed on resolve/reject/cancel
190+
* @param {function(value)} handler
191+
* @return {this}
192+
*/
193+
function always(handler) {
194+
this._handlers.push({
195+
"resolve": handler,
196+
"reject": handler,
197+
"cancel": handler
198+
});
199+
200+
this._callHandlers();
201+
return this;
202+
}
203+
204+
/**
205+
* Cancel a promise
206+
* - No .then/.fail/.finally handlers will be called
207+
* - .cancelled handler will be called
208+
* @param {*} reason - value that will be passed to .cancelled handler
209+
*/
210+
function cancel(reason = null) {
211+
if (this.STATE_PENDING == this._state) {
212+
this._state = this.STATE_CANCELLED;
213+
this._value = reason;
214+
this._callHandlers();
215+
}
216+
}
217+
218+
/**
219+
* While loop with Promise's
220+
* Stops on continueCallback() == false or first rejection of looped Promise
221+
*
222+
* @param {function:boolean} condition - if returns false, loop stops
223+
* @param {function:Promise} next - function to get next promise in the loop
224+
* @return {Promise} Promise that is resolved/rejected with the last value that come from looped promise when loop finishes
225+
*/
226+
static function loop(condition, next) {
227+
return (this)(function (resolve, reject) {
228+
229+
local doLoop;
230+
local lastResolvedWith;
231+
232+
doLoop = function() {
233+
if (condition()) {
234+
next().then(
235+
function (v) {
236+
lastResolvedWith = v;
237+
imp.wakeup(0, doLoop)
238+
},
239+
reject
240+
);
241+
} else {
242+
resolve(lastResolvedWith);
243+
}
244+
}
245+
246+
imp.wakeup(0, doLoop);
247+
248+
}.bindenv(this));
249+
}
250+
251+
/**
252+
* Returns Promise that resolves when
253+
* all promises in chain resolve:
254+
* one after each other
255+
*
256+
* @param {{Promise|function}[]} promises - array of Promises/functions that return Promises
257+
* @return {Promise} Promise that is resolved/rejected with the last value that come from looped promise
258+
*/
259+
static function serial(promises) {
260+
local i = 0;
261+
return this.loop(
262+
@() i < promises.len(),
263+
function () {
264+
return "function" == type(promises[i])
265+
? promises[i++]()
266+
: promises[i++];
267+
}
268+
)
269+
}
270+
271+
/**
272+
* Execute Promises in parallel.
273+
*
274+
* @param {{Primise|functiuon}[]} promises
275+
* @param {wait} wait - wait for all promises to finish?
276+
* @returns {Promise}
277+
*/
278+
static function _parallel(promises, wait) {
279+
return (this)(function (resolve, reject) {
280+
local resolved = 0;
281+
282+
local checkDone = function(v = null) {
283+
if ((!wait && resolved == 1) || (wait && resolved == promises.len())) {
284+
resolve(v);
285+
return true;
286+
}
287+
}
288+
289+
if (!checkDone()) {
290+
for (local i = 0; i < promises.len(); i++) {
291+
(
292+
"function" == type(promises[i])
293+
? promises[i]()
294+
: promises[i]
295+
)
296+
.then(function (v) {
297+
resolved++;
298+
checkDone(v);
299+
}, reject);
300+
}
301+
}
302+
303+
}.bindenv(this));
304+
}
305+
306+
/**
307+
* Execute Promises in parallel and resolve when they are all done.
308+
* Returns Promise that resolves with last paralleled Promise value
309+
* or rejects with first rejected paralleled Promise value.
310+
*
311+
* @param {{Primise|functiuon}[]} promises
312+
* @returns {Promise}
313+
*/
314+
static function parallel(promises) {
315+
return this._parallel(promises, true);
316+
}
317+
318+
/**
319+
* Execute Promises in parallel and resolve when the first is done.
320+
* Returns Promise that resolves/rejects with the first
321+
* resolved/rejected Promise value.
322+
*
323+
* @param {{Primise|functiuon}[]} promises
324+
* @returns {Promise}
325+
*/
326+
static function first(promises) {
327+
return this._parallel(promises, false);
328+
}
329+
}

0 commit comments

Comments
 (0)