var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
Promise.all([p1,p2,p3]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
How can i wait for 2 promises to get completed ? Promise.race() wait for one promise to get completed.
Edit
I have n number of promises, what i want to achieve is wait for first k number of promises to get resolved and than trigger some event. assume k < n
Edit - 2
I am sure that k number of promise will be successfully resolved out of n numbers of promise given
(Note: Bluebird has a built-in helper function that serves precisely this purpose and has more or less the same behavior as my waitForN method below, so that option is always available)
I don't believe ES6 promises have an elegant built-in way to do this, but you could define the following relatively short now somewhat long helper function to take care of this.
Edit: Added an additional helper function that includes the indices of the successful promises in the result.
Edit: Updated to reject if the number of rejections reaches the point where successful resolution would be impossible.
Edit: Updated to more gracefully work with promises if it is iterable and check edge cases where the result should immediately resolve (e.g. if n === 0)
function waitForN(n, promises) {
let resolved = [];
let failCount = 0;
let pCount = 0;
return new Promise((resolve, reject) => {
const checkSuccess = () => {
if (resolved.length >= n) { resolve(resolved); }
};
const checkFailure = () => {
if (failCount + n > pCount) {
reject(new Error(`Impossible to resolve successfully. n = ${n}, promise count = ${pCount}, failure count = ${failCount}`));
}
};
for (let p of promises) {
pCount += 1;
Promise.resolve(p).then(r => {
if (resolved.length < n) { resolved.push(r); }
checkSuccess();
}, () => {
failCount += 1;
checkFailure();
});
}
checkFailure();
checkSuccess();
});
}
const waitForNWithIndices = (n, promises) =>
waitForN(n, promises.map((p, index) =>
Promise.resolve(p).then(result => ({ index, result }))
));
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 1500, 'four');
});
waitForN(2, [p1,p2,p3,p4]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
waitForNWithIndices(2, [p1,p2,p3,p4]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
You could use a higher order function to bake in the n then run all promises waiting for n to finish
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
const raceN = n => (...promises) => {
const resolved = []
return new Promise((res, rej) =>
promises.forEach(promise =>
promise.then(x => {
resolved.push(x)
if (resolved.length === n)
res(resolved)
})
.catch(rej)
)
)
}
const race2 = raceN(2)
race2(p1, p2, p3)
.then(results => console.log(results))
race2(Promise.resolve('resolved'), Promise.reject('rejected'))
.then(results => console.log(results))
.catch(reason => console.log(reason))
Given my many comments on JLRishe's answer, I'll also post my subtly different version of the function:
function any(k, iterable) {
const results = [];
let fullfilled = 0, rejected = 0, n = 0;
return new Promise((resolve, reject) => {
k = Math.max(0, Math.floor(k));
function check() {
if (fulfilled == k)
resolve(results);
else if (rejected + k == n + 1)
reject(new Error(`No ${k} of ${n} got fulfilled`));
}
for (const thenable of iterable) {
const i = n++;
Promise.resolve(thenable).then(res => {
if (fulfilled++ < k) results[i] = res;
check();
}, () => {
rejected++;
check();
});
}
check();
});
}
A bit late but maybe this will do it for you:
const first = howMany => (promises,resolved=[],rejected=[]) => {
if(promises.length-rejected.length<howMany){
return Promise.reject([resolved,rejected]);
}
if(resolved.length===howMany){
return Promise.resolve(resolved);
}
if(resolved.length===0&&rejected.length===0){
promises=promises.map(
(p,index)=>Promise.resolve(p)
.then(resolve=>[resolve,index])
.catch(err=>Promise.reject([err,index]))
);
}
return Promise.race(promises)
.then(
([resolve,index])=>
first(howMany)(
promises.filter(
(p,i)=>i!==index
),
resolved.concat([resolve]),
rejected
)
)
.catch(
([err,index])=>
first(howMany)(
promises.filter(
(p,i)=>i!==index
),
resolved,
rejected.concat([err])
)
);
};
const first2 = first(2);
first2([p1,p2,p3])
.then(
result=>//you will have an array of 2 with resolve value(s)
)
.catch(
err=>//you will have 2 arrays, resolved ones and errors
)
EDIT: Updated to match question edits.
Pretty easy to roll your own when the abstraction limits your expressiveness.
This code is short and can easily be reworked to run in legacy ES3 environments.
You can also easily expand its usefulness by adding code to cancel unresolved timeouts. This is what you get when you don't hand everything over to a library; greater simplicity and more capability.
function raceN(n, fns, resolve, reject) {
const res = [], rej = [];
let halt = false;
for (const [idx, fn] of fns.entries()) {
fn(data => update(res, data, idx), data => update(rej, data, idx));
}
function update(arr, data, idx) {
if (halt) return;
arr.push({idx, data});
if ((halt=res.length >= n)) resolve(res);
else if ((halt=rej.length > fns.length - n)) reject(rej);
}
}
DEMO:
function raceN(n, fns, resolve, reject) {
const res = [], rej = [];
let halt = false;
for (const [idx, fn] of fns.entries()) {
fn(data => update(res, data, idx), data => update(rej, data, idx));
}
function update(arr, data, idx) {
if (halt) return;
arr.push({idx, data});
if ((halt=res.length >= n)) resolve(res);
else if ((halt=rej.length > fns.length - n)) reject(rej);
}
}
function rand() { return Math.ceil(Math.random() * 5000) }
var fns = [(resolve, reject) => {
setTimeout(resolve, rand(), 'one');
},
(resolve, reject) => {
setTimeout(resolve, rand(), 'two');
},
(resolve, reject) => {
setTimeout(reject, rand(), 'three');
}];
raceN(2, fns, values => {
console.log("SUCCESS:", values);
}, reason => {
console.log("REJECT:", reason)
});
If you're guaranteed that at least n elements will succeed, it then becomes even shorter and simpler.
function raceN(n, fns, resolve, reject) {
const res = [];
for (const [idx, fn] of fns.entries()) {
fn(data => res.length < n && res.push({idx, data}) == n) && resolve(res),
data => reject({idx, data}));
}
}
DEMO:
function raceN(n, fns, resolve, reject) {
const res = [];
for (const [idx, fn] of fns.entries()) {
fn(data => res.length < n && res.push({idx, data}) == n) && resolve(res),
data => reject({idx, data}));
}
}
function rand() { return Math.ceil(Math.random() * 4000) }
var fns = [(resolve, reject) => {
setTimeout(resolve, rand(), 'one');
},
(resolve, reject) => {
setTimeout(resolve, rand(), 'two');
},
(resolve, reject) => {
setTimeout(reject, rand(), 'three');
}];
raceN(2, fns, values => {
console.log("SUCCESS:", values);
}, reason => {
console.log("REJECTING:", reason)
});
One other idea could be using Promise.reject() to cut it short when the condition is met while catching possible errors thrown, could be as follows;
var p1 = new Promise((v, x) => setTimeout(v, 1000, 'one')),
p2 = new Promise((v, x) => setTimeout(v, 5000, 'two')),
p3 = new Promise((v, x) => setTimeout(v, 1500, 'three')),
pn = (n, ps, k = 0, r = []) => Promise.all(ps.map(p => p.then(v => (k === n - 1 ? Promise.reject(r.concat(v))
: (++k, r.push(v))))))
.catch(r => Array.isArray(r) ? r : Promise.reject(r));
pn(2,[p1,p2,p3]).then(rs => console.log(rs))
.catch(e => console.log(e));
A more modern approach, in 2022, using iter-ops library:
import {pipeAsync, take, waitRace} from 'iter-ops';
// easy-to-read way to define a job function:
const job = (delay, value) => new Promise(resolve => {
setTimeout(() => resolve(value), delay);
});
// list of jobs:
const input = [
job(1000, 'one'),
job(2000, 'two'),
job(3000, 'three')
];
const i = pipeAsync(
input, // our input
waitRace(10), // race-resolve with cache up to 10 items
take(2) // take only the first 2 items
).catch(err => {
console.log(err); // report any iteration-time error
});
// iterating through our list of jobs:
(async function () {
for await(const a of i) {
console.log(a);
}
})();
This will output one and two. You can twist the delays to see the result change accordingly.
CREDITS
I am the author of the library, while operator waitRace was implemented by #Bergi.
Related
The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
Taken from MDN site.
I have 5 promises and I need to know once any 2 promises are resolved, taking performance under consideration.
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
async function swimmer (name) {
const start = Date.now()
console.log(`${name} started the race`)
await sleep(Math.random() * 5000)
console.log(`${name} finished the race`)
return { name, delta: Date.now() - start }
}
const swimmers =
[ swimmer("Alice"), swimmer("Bob"), swimmer("Claire"), swimmer("David"), swimmer("Ed") ];
Promise.race(swimmers)
.then(({ name }) => console.log(`*** ${name} is the winner!!! ***`))
.catch(console.error)
This will return the fastest swimmer but I would like to print once I get 2 promises resolved.
How can I do it?
You could write a custom implementation of Promise.race that returns a promise that is resolved with the result of 2 promises that are resolved before others.
Following code example shows an implementation:
function customPromiseRace(promiseArr, expectedCount) {
return new Promise((resolve, reject) => {
if (promiseArr.length < expectedCount) {
throw new Error(`Not enough promises to get ${expectedCount} results`);
}
// array to store the results of fulfilled promises
const results = [];
for (const p of promiseArr) {
Promise.resolve(p).then(result => {
// push the promise fulfillment value to the "results"
// array only if we aren't already finished
if (results.length < expectedCount) {
results.push(result);
if (results.length === expectedCount) {
resolve(results);
}
}
}, reject);
}
});
}
Demo
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function swimmer(name) {
const start = Date.now();
console.log(`${name} started the race`);
await sleep(Math.random() * 5000);
console.log(`${name} finished the race`);
return { name, delta: Date.now() - start };
}
const swimmers = [
swimmer('Alice'),
swimmer('Bob'),
swimmer('Claire'),
swimmer('David'),
swimmer('Ed'),
];
function customPromiseRace(promiseArr, expectedCount) {
return new Promise((resolve, reject) => {
if (promiseArr.length < expectedCount) {
throw new Error(`Not enough promises to get ${expectedCount} results`);
}
const results = [];
for (const p of promiseArr) {
Promise.resolve(p).then(result => {
if (results.length < expectedCount) {
results.push(result);
if (results.length === expectedCount) {
resolve(results);
}
}
}, reject);
}
});
}
customPromiseRace(swimmers, 2).then(console.log).catch(console.error);
You could use .then(handleMyPromise) inside of Promise.race.
Example:
let handlepromise1 = function ( response ){
console.log("Expected response: ",response);
return response;
}
let promise1 = function () {
return new Promise((r) => {
setTimeout(()=>{
console.log("Promise1 says");
r("promise1")
},2000)
})
}
let timeout = function(){
return new Promise((r) => {
setTimeout(()=>{
console.log("timeout says");
r("timeout")
},3000)
});
};
(async function(){
let func = async function tt(){
let r = await Promise.race([
promise1().then(handlepromise1),
timeout()
])
console.log("Result: ",r);
}
func()
})()
Result of console:
Promise1 says
Expected response: promise1
Result: promise1
timeout says
See the code below
var arr = await [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
console.log(arr); // <-- [Promise, Promise, Promise ....]
// i would expect it to return [1,2,3,4,5]
Quick edit:
The accepted answer is correct, by saying that map doesnt do anything special to async functions. I dont know why i assumed it recognizes async fn and knows to await the response.
I was expecting something like this, perhaps.
Array.prototype.mapAsync = async function(callback) {
arr = [];
for (var i = 0; i < this.length; i++)
arr.push(await callback(this[i], i, this));
return arr;
};
var arr = await [1,2,3,4,5].mapAsync(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
// outputs 1, 2 ,3 ... with 1 second intervals,
// arr is [1,2,3,4,5] after 5 seconds.
Because an async function always returns a promise; and map has no concept of asynchronicity, and no special handling for promises.
But you can readily wait for the result with Promise.all:
try {
const results = await Promise.all(arr);
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
(async() => {
try {
console.log(await Promise.all(arr));
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
})();
.as-console-wrapper {
max-height: 100% !important;
}
or using Promise syntax
Promise.all(arr)
.then(results => {
// Use `results`, which will be an array
})
.catch(err => {
// Handle error
});
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Promise.all(arr)
.then(results => {
console.log(results);
})
.catch(err => {
// Handle error
});
.as-console-wrapper {
max-height: 100% !important;
}
Side note: Since async functions always return promises, and the only thing you're awaiting in your function is a promise you create, it doesn't make sense to use an async function here anyway. Just return the promise you're creating:
var arr = [1,2,3,4,5].map((index) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Of course, if you're really doing something more interesting in there, with awaits on various things (rather than just on new Promise(...)), that's different. :-)
Since it is async, the values have not been determined at the time map returns. They won't exist until the arrow function has been run.
This is why Promises exist. They are a promise of a value being available in the future.
I need to chain promises which are using request promises, so it is kinda chaining nested promises.
Imagine the code:
const rp = require('request-promise');
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
methodA();
}
};
function methodA(){
let options = {...};
rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
};
function methodB(resultA){
let options = {uri: resultA};
rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
};
function methodC(resultA, resultB){
//some calculations
};
In doStuff I need to wait for result of all ten executions of methodC and collect them into array. I have tried to chain it like that:
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
let promiseA = new Promise((resolve, reject) => {
resolve(methodA());
});
let promiseB = promiseA.then(result => methodB(result));
let promiseC = promiseB.then(result => methodC(promiseA.result, result));
Promise.all([promiseA, promiseB, promiseC]);
}
};
But for sure it won't work, because in methodA and methodB we have HTTP requests which are asynchronous. Therefore, result in promiseB is undefined.
It means, the question is: how to chain promises, if they have nested promises? (and how to collect result in the end?)
Thanks!
UPDATE: Chaining promises also not much of the help, as 1 is returned prior array of AB's, but desired result is vice versa:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
Promise.all(promises).then(results => {
console.log(results);
});
return 1;
}
console.log(doStuff());
Each of your functions needs to return their promise:
function methodA(){
let options = {...};
return rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let options = {uri: resultA};
return rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
//some calculations
}
function doStuff() {
let promises = [];
for ( let i = 0; i <= 10; i++ ){
promises.push(methodA());
}
Promise.all(promises).then(...)
}
Edit: I created a test example, which creates promises in methodA and methodB. Each promise lasts some amount of time from 0 to 2 seconds. It seems to be working:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
return Promise.all(promises).then(results => {
console.log(results);
return 1;
});
}
doStuff().then(result => {
console.log(result);
});
Answer by #Frank_Modica is the way to go.
Just want to add that if you enable async/await you could do it like this. But it does require Babel or Typescript:
async function myMethod() {
const options = { }
try {
const result_a = await rp(options)
const result_b = await rp({ uri: result_a })
const result_c = ...
} catch(err) {
console.log(err)
}
}
for (let i = 0; i <= 10; i++) {
await myMethod();
}
It has to be lightly changed to:
Promise.all([promiseA, promiseB, promiseC]).then([promiseD]);
Also in the functions itself it has to be a return statement to make them chained. For the request itself, just add: {async: false}
Also it can be used:
var runSequence = require('run-sequence');
function methodA () {
return runSequence(
'promiseA',
'promiseB',
['promiseC1', 'promiseC2'],
'promiseD',
callback
);
}
Here is my situation:
fetchData(foo).then(result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + bar);
}, 0)
});
}).then(result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve( result + woo);
}, 0)
});
}).then(result => {
setTimeout(() => {
doSomething(result);
}, 0)
});
Where each setTimeout is a different async operation using the callback pattern.
It is really painfull to wrap each function inside a promise, I feel like the code should look more like this:
fetchData(foo).then(result => {
setTimeout(() => {
return result + bar;
}, 0)
}).then(result => {
setTimeout(() => {
return result + woo;
}, 0)
}).then(result => {
setTimeout(() => {
doSomething(result);
}, 0)
});
Obviously this doesn't work.
Am I using Promises right? Do I really have to wrap all existing async function in promises?
EDIT:
Actually I realize my example was not totally reprensentative of my situation, I did not make it clear that the setTimeout in my example is meant to reprensent en async operation. This situation is more representative of my situation:
fetchData(foo).then(result => {
return new Promise((resolve, reject) => {
asyncOperation(result, operationResult => {
resolve(operationResult);
}, 0)
});
}).then(result => {
return new Promise((resolve, reject) => {
otherAsyncOperation(result, otherAsyncResult => {
resolve( otherAsyncResult);
}, 0)
});
}).then(result => {
doSomething(result);
});
Am I using Promises right? Do I really have to wrap all existing async function in promises?
Yes. Yes.
I feel like the code should look more like this
No, it shouldn't. It rather should look like this:
function promiseOperation(result) {
return new Promise((resolve, reject) => {
asyncOperation(result, resolve, 0)
});
}
function otherPromiseOperation(result) {
return new Promise((resolve, reject) => {
otherAsyncOperation(result, resolve, 0)
});
}
fetchData(foo).then(promiseOperation).then(otherPromiseOperation).then(doSomething);
It is really painfull to wrap each function inside a promise
Well, don't repeatedly write it out every time. You can abstract this wrapping into a function!
function promisify(fn) {
return value => new Promise(resolve => {
fn(value, resolve, 0)
});
}
const promiseOperation = promisify(asyncOperation);
const otherPromiseOperation = promisify(otherAsyncOperation);
fetchData(foo).then(promiseOperation).then(otherPromiseOperation).then(doSomething);
Notice that most promise libraries come with a such a promisification function included, so your whole code reduces to these three lines.
You are using promise right. Just a small note on the first snippet of code: you are not returning a promise from the last then() callback:
...
}).then(result => {
setTimeout(() => {
doSomething(result);
}, 0)
});
This is correct if you need simply to do an async operation without returning to the caller of fetchData the value of the last async operation. If you need to return this value, you need to convert to promise this operation too:
fetchData(foo).then(result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + bar);
}, 0)
});
}).then(result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + woo);
}, 0)
});
}).then(result => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(doSomething(result));
}, 0)
});
});
Here I suppose doSomething is a sync function returning a value.
Said so, if you want to reduce the noise of create the promise to wrap setTimeout every time, you can create a utility function setTimeoutWithPromise:
function setTimeoutWithPromise(operation, millisec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(operation());
}, millisec)
});
}
And clean your code:
fetchData(foo)
.then(result => setTimeoutWithPromise(() => result + bar, 0))
.then(result => setTimeoutWithPromise(() => result + woo, 0))
.then(result => setTimeoutWithPromise(() => doSomething(result), 0));
I know this isn't in the scope of a Array.map but I'd like to wait until the previous item has finished its promise before starting the next one. It just happens that I need to wait for the previous entry to be saved in the db before moving forwards.
const statsPromise = stats.map((item) => {
return playersApi.getOrAddPlayer(item, clubInfo, year); //I need these to wait until previous has finished its promise.
});
Promise.all(statsPromise)
.then((teamData) => {
..//
});
playersApi.getOrAddPlayer returns a new Promise
Edit
Reading more on it, it seems its important to show playersApi.getOrAddPlayer
getOrAddPlayer: function (item, clubInfo, year) {
return new Promise((resolve, reject) => {
var playerName = item.name.split(' '),
fname = playerName[0].caps(),
sname = playerName[1].caps();
Players.find({
fname: fname,
sname: sname,
}).exec()
.then(function(playerDetails, err){
if(err) reject(err);
var savePlayer = new Players();
//stuff
savePlayer.save()
.then(function(data, err){
if(err) reject(err);
item._id = data._id;
resolve(item);
});
});
});
}
You can use reduction instead of mapping to achieve this:
stats.reduce(
(chain, item) =>
// append the promise creating function to the chain
chain.then(() => playersApi.getOrAddPlayer(item, clubInfo, year)),
// start the promise chain from a resolved promise
Promise.resolve()
).then(() =>
// all finished, one after the other
);
Demonstration:
const timeoutPromise = x => {
console.log(`starting ${x}`);
return new Promise(resolve => setTimeout(() => {
console.log(`resolving ${x}`);
resolve(x);
}, Math.random() * 2000));
};
[1, 2, 3].reduce(
(chain, item) => chain.then(() => timeoutPromise(item)),
Promise.resolve()
).then(() =>
console.log('all finished, one after the other')
);
If you need to accumulate the values, you can propagate the result through the reduction:
stats
.reduce(
(chain, item) =>
// append the promise creating function to the chain
chain.then(results =>
playersApi.getOrAddPlayer(item, clubInfo, year).then(data =>
// concat each result from the api call into an array
results.concat(data)
)
),
// start the promise chain from a resolved promise and results array
Promise.resolve([])
)
.then(results => {
// all finished, one after the other
// results array contains the resolved value from each promise
});
Demonstration:
const timeoutPromise = x => {
console.log(`starting ${x}`);
return new Promise(resolve =>
setTimeout(() => {
console.log(`resolving result for ${x}`);
resolve(`result for ${x}`);
}, Math.random() * 2000)
);
};
function getStuffInOrder(initialStuff) {
return initialStuff
.reduce(
(chain, item) =>
chain.then(results =>
timeoutPromise(item).then(data => results.concat(data))
),
Promise.resolve([])
)
}
getStuffInOrder([1, 2, 3]).then(console.log);
Variation #1: Array.prototype.concat looks more elegant but will create a new array on each concatenation. For efficiency purpose, you can use Array.prototype.push with a bit more boilerplate:
stats
.reduce(
(chain, item) =>
chain.then(results =>
playersApi.getOrAddPlayer(item, clubInfo, year).then(data => {
// push each result from the api call into an array and return the array
results.push(data);
return results;
})
),
Promise.resolve([])
)
.then(results => {
});
Demonstration:
const timeoutPromise = x => {
console.log(`starting ${x}`);
return new Promise(resolve =>
setTimeout(() => {
console.log(`resolving result for ${x}`);
resolve(`result for ${x}`);
}, Math.random() * 2000)
);
};
function getStuffInOrder(initialStuff) {
return initialStuff
.reduce(
(chain, item) =>
chain.then(results =>
timeoutPromise(item).then(data => {
results.push(data);
return results;
})
),
Promise.resolve([])
);
}
getStuffInOrder([1, 2, 3]).then(console.log);
Variation #2: You can lift the results variable to the upper scope. This would remove the need to nest the functions to make results available via the nearest closure when accumulating data and instead make it globally available to the whole chain.
const results = [];
stats
.reduce(
(chain, item) =>
chain
.then(() => playersApi.getOrAddPlayer(item, clubInfo, year))
.then(data => {
// push each result from the api call into the globally available results array
results.push(data);
}),
Promise.resolve()
)
.then(() => {
// use results here
});
Demonstration:
const timeoutPromise = x => {
console.log(`starting ${x}`);
return new Promise(resolve =>
setTimeout(() => {
console.log(`resolving result for ${x}`);
resolve(`result for ${x}`);
}, Math.random() * 2000)
);
};
function getStuffInOrder(initialStuff) {
const results = [];
return initialStuff.reduce(
(chain, item) =>
chain
.then(() => timeoutPromise(item))
.then(data => {
results.push(data);
return results;
}),
Promise.resolve()
);
}
getStuffInOrder([1, 2, 3]).then(console.log);
If you are fine with using promise library, you can use Promise.mapSeries by Bluebird for this case.
Example:
const Promise = require("bluebird");
//iterate over the array serially, in-order
Promise.mapSeries(stats, (item) => {
return playersApi.getOrAddPlayer(item, clubInfo, year));
}).then((teamData) => {
..//
});
You can use a recursion solution
const statsPromise = (function s(p, results) {
return p.length ? playersApi.getOrAddPlayer(p.shift(), clubInfo, year) : results;
})(stats.slice(0), []);
statsPromise
.then((teamData) => {
//do stuff
});
let n = 0;
let promise = () => new Promise(resolve =>
setTimeout(resolve.bind(null, n++), 1000 * 1 + Math.random()));
let stats = [promise, promise, promise];
const statsPromise = (function s(p, results) {
return p.length ? p.shift().call().then(result => {
console.log(result);
return s(p, [...results, result])
}) : results;
})(stats.slice(0), []);
statsPromise.then(res => console.log(res))
You could use a kind of recursion:
function doStats([head, ...tail]) {
return !head ? Promise.resolve() :
playersApi.getOrAddPlayer(head, clubInfo, year)
.then(() => doStats(tail));
}
doStats(stats)
.then(() => console.log("all done"), e => console.log("something failed", e));
Another classic approach is to use reduce:
function doStats(items) {
return items.reduce(
(promise, item) =>
promise.then(() => playersApi.getOrAddPlayer(item, clubInfo, year)),
Promise.resolve());
By the way, you could clean up your getOrAddPlayer function quite a bit, and avoid the promise constructor anti-pattern, with:
getOrAddPlayer: function (item, clubInfo, year) {
var playerName = item.name.split(' '),
fname = playerName[0].caps(),
sname = playerName[1].caps();
return Players.find({fname, sname}).exec()
.then(playerDetails => new Players().save())
.then({_id} => Object.assign(item, {_id}));
}
I gave it a thought but I didn't find a better method than the reduce one.
Adapted to your case it would be something like this:
const players = [];
const lastPromise = stats.reduce((promise, item) => {
return promise.then(playerInfo => {
// first iteration will be undefined
if (playerInfo) {
players.push(playerInfo)
}
return playersApi.getOrAddPlayer(item, clubInfo, year);
});
}, Promise.resolve());
// assigned last promise to a variable in order to make it easier to understand
lastPromise.then(lastPlayer => players.push(lastPlayer));
You can see some explanation about this here.