Skip to content

Commit 3759f02

Browse files
author
Jason Walton
committed
Add whilst, doWhilst.
1 parent 7d223cf commit 3759f02

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Then somewhere in your node.js application:
4141
* [`parallel`](#parallel), `parallelLimit`
4242
* [`series`](#series)
4343
* [`timeout`](#timeout)
44+
* [`whilst`](#whilst), `doWhilst`
4445

4546

4647
# Utilities
@@ -119,7 +120,6 @@ Example:
119120
});
120121

121122

122-
123123
<a name="timeout"/>
124124
### timeout(promise, ms)
125125

@@ -136,3 +136,28 @@ Example:
136136
// Will reject if `doSomething` takes longer than 500 milliseconds.
137137
promiseTools.timeout(doSomething(), 500)
138138
.then(...)
139+
140+
<a name="whilst"/>
141+
### whilst(fn, test), doWhilst(test, fn)
142+
143+
While the synchronous function `test()` returns true, `whilst` will continuously execute `fn()`. `fn()` should return
144+
a Promise. `whilst` will resolve to the same value as the final call to `fn()`. If `fn()` or `test()` throw an error,
145+
then `whilst()` will reject immediately.
146+
147+
`doWhilst()` is similar to `whilst()`, but `whilst()` might execute `fn()` zero times if `test()` returns false on the
148+
first run, where `doWhilst()` is guaranteed to call `fn()` at least once. Note that the parameters are reversed
149+
between `whilst()` and `doWhilst()`.
150+
151+
Example:
152+
153+
var count = 0;
154+
promiseTools.whilst(
155+
function() {
156+
count++;
157+
return Promise.resolve(count);
158+
},
159+
function() {return count > 10;}
160+
)
161+
.then(function(result) {
162+
// result will be 10 here.
163+
});

src/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,47 @@ exports.timeout = (p, ms) =>
146146
}
147147
);
148148
});
149+
150+
/*
151+
* Continually call `fn()` while `test()` returns true.
152+
*
153+
* `fn()` should return a Promise. `test()` is a synchronous function which returns true of false.
154+
*
155+
* `whilst` will resolve to the last value that `fn()` resolved to, or will reject immediately with an error if
156+
* `fn()` rejects or if `fn()` or `test()` throw.
157+
*/
158+
exports.whilst = (test, fn) =>
159+
new Promise((resolve, reject) => {
160+
let lastResult = null;
161+
let doIt = () => {
162+
try {
163+
if(test()) {
164+
Promise.resolve()
165+
.then(fn)
166+
.then(
167+
(result) => {
168+
lastResult = result;
169+
setTimeout(doIt, 0);
170+
},
171+
reject
172+
);
173+
} else {
174+
resolve(lastResult);
175+
}
176+
} catch (err) {
177+
reject(err);
178+
}
179+
};
180+
181+
doIt();
182+
});
183+
184+
exports.doWhilst = (fn, test) => {
185+
let first = true;
186+
let doTest = () => {
187+
let answer = first || test();
188+
first = false;
189+
return answer;
190+
};
191+
return exports.whilst(doTest, fn);
192+
};

test/whilst.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use strict"
2+
3+
let chai = require('chai');
4+
chai.use(require('chai-as-promised'));
5+
let expect = chai.expect;
6+
let promiseTools = require('../src');
7+
8+
describe('whilst', () => {
9+
10+
it('should execute a function over and over until the test returns true', () => {
11+
let count = 0;
12+
return expect(
13+
promiseTools.whilst(
14+
() => count < 10,
15+
() => {
16+
count++;
17+
return Promise.resolve(count);
18+
}
19+
)
20+
).to.eventually.equal(10)
21+
.then( () => expect(count).to.equal(10) );
22+
});
23+
24+
it('should work, even if fn does not return a promise', () => {
25+
let count = 0;
26+
return expect(
27+
promiseTools.whilst(
28+
() => count < 10,
29+
() => {
30+
count++;
31+
return count;
32+
}
33+
)
34+
).to.eventually.equal(10)
35+
});
36+
37+
it('should return `null` if `test` returns false immediately', () => {
38+
let fnCalled = false;
39+
return expect(
40+
promiseTools.whilst(
41+
() => false,
42+
() => {fnCalled = true;}
43+
)
44+
).to.eventually.equal(null)
45+
.then(() => expect(fnCalled).to.be.false)
46+
});
47+
48+
it('should reject if fn rejects', () => {
49+
let count = 0;
50+
return expect(
51+
promiseTools.whilst(
52+
() => count < 10,
53+
() => Promise.reject(new Error('foo'))
54+
)
55+
).to.be.rejectedWith('foo');
56+
});
57+
58+
it('should reject if fn throws', () => {
59+
let count = 0;
60+
return expect(
61+
promiseTools.whilst(
62+
() => count < 10,
63+
() => {
64+
throw new Error('foo');
65+
}
66+
)
67+
).to.be.rejectedWith('foo');
68+
});
69+
70+
it('should reject if test throws', () => {
71+
return expect(
72+
promiseTools.whilst(
73+
() => {throw new Error('foo');},
74+
() => {}
75+
)
76+
).to.be.rejectedWith('foo');
77+
});
78+
79+
});
80+
81+
describe('doWhilst', () => {
82+
83+
it('should execute a function over and over until the test returns true', () => {
84+
let count = 0;
85+
return expect(
86+
promiseTools.doWhilst(
87+
() => {
88+
count++;
89+
return Promise.resolve(count);
90+
},
91+
() => count < 10
92+
)
93+
).to.eventually.equal(10)
94+
.then( () => expect(count).to.equal(10) );
95+
});
96+
97+
it('should call fn once, even if test returns false immediately', () => {
98+
let fnCount = 0;
99+
return expect(
100+
promiseTools.doWhilst(
101+
() => {
102+
fnCount++;
103+
return "hello";
104+
},
105+
() => false
106+
)
107+
).to.eventually.equal("hello")
108+
.then( () => expect(fnCount).to.equal(1) );
109+
});
110+
111+
});

0 commit comments

Comments
 (0)