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);