Reputation: 7649
var myArray = [1, 2, 3, 4, 5, 6]
function myPromise(num){
return new Promise(res => {
window.setTimeout(()=>{
res( console.log("done: " + num) )
},2000)
})
}
myPromise(myArray[0])
.then(x => myPromise(myArray[1]))
.then(x => myPromise(myArray[2]))
.then(x => myPromise(myArray[3]))
.then(x => myPromise(myArray[4]))
.then(x => myPromise(myArray[5]))
Right now, if I execute the statement above, it will run sequentially. In my actual use case the array is dynamically populated and I need to execute the myPromise()
function for each member in myArray
.
How can I make a "pauseable loop" that will loop for each item in the array, execute myPromise
and wait for the promise to be resolved before continuing to the next iteration?
Upvotes: 92
Views: 90137
Reputation: 667
We can use a loop or recursion to execute the promises one by one(sequence). Here's an example using a recursive function
// Example array of other tasks (task should be a promises)
const promises = [
() => asyncFunction1(),
() => asyncFunction2(),
() => asyncFunction3(),
// Add more functions if needed
];
The Recursive function
async function executePromisesInSequence(promises) {
if (promises.length === 0) {
return;
}
const promise = promises.shift(); // Get the first task to be executed
await promise(); // Execute the task and wait for it to complete
await executePromisesInSequence(promises); // Recursively call the function with the remaining tasks
}
Finally call function to start running the promises in sequence
executePromisesInSequence(promises)
.then(() => {
console.log("All promises are completed.");
})
.catch((err) => {
console.error("An error occurred:", err);
});
Upvotes: -1
Reputation: 23
I enhanced the solution of Master Chief to also return the results as Array. This would be the TypeScript answer:
/**
* Executes a list of promises sequentially.
*
* Accepts a list of promise factories.
*
* @example
* ```
* const promiseCreator = (i: number, time: number, text: string) => {
* return new Promise(resolve => setTimeout(
* () => resolve(console.log(`${i} ${text}`)),
* time)
* );
* };
*
* const promiseFactories = [
* () => promiseCreator(1, 1000, "sequential"),
* () => promiseCreator(2, 1000, "sequential"),
* () => promiseCreator(3, 1000, "sequential"),
* () => promiseCreator(4, 1000, "sequential"),
* () => promiseCreator(5, 1000, "sequential"),
* ];
*
* sequentialPromises(promiseFactories);
* ```
*
* @template T
* @param {(() => Promise<T>)[]} promiseFactories
* @return {Promise<T[]>}
*/
export const sequentialPromises = <T>(promiseFactories: (() => Promise<T>)[]): Promise<T[]> | undefined => {
let promiseChain: Promise<T> | undefined;
const results: T[] = [];
promiseFactories.forEach((promiseFactory) => {
promiseChain = (!promiseChain ? promiseFactory() : promiseChain.then(promiseFactory)).then((result) => {
results.push(result);
return result;
});
});
return promiseChain?.then(() => results);
};
Upvotes: 2
Reputation: 608
Here's a concise way using Array.reduce and async/await. Taking the eg array and myPromise function from OP:
var myArray = [1, 2, 3, 4, 5, 6];
function myPromise(num) {
return new Promise((res) => {
setTimeout(() => {
res(console.log("done: " + num));
}, 2000);
});
}
if your myPromise fn does not return result, then
myArray.reduce(async (a, b) => {
await a;
await myPromise(b);
}, null);
this can be easily modified to accumulate result. Eg, if your myPromise were to return a result, you could accumulate them in an array, in the order of myArray elements:
const resultArray = myArray.reduce(
async (a, b) => [...(await a), await myPromise(b)],
[]
);
or an object
const resultObject = myArray.reduce(async (a, b) => ({
async (a, b) => ({
...(await a),
[b]: await myPromise(b),
}),
{}
);
Upvotes: -1
Reputation: 19070
You can iterate over the array of elements and pass the params like this:
const arr = [1, 2, 3, 4, 5, 6]
const MyPromiseFunction = num => new Promise(
(resolve, reject) =>
// Your logic...
setTimeout(() => num <= 4
? resolve('Success!')
: reject('Rejected!'), 1000 * num)
)
const logMessage = (num, msg) =>
console.log(`For number ${num} promise result: ${msg}`)
arr.map(
async (num) => await MyPromiseFunction(num)
.then(message => logMessage(num, message))
.catch(reason => logMessage(num, reason))
)
Upvotes: -2
Reputation: 224913
You can make the repeated application of .then
into a fold pretty neatly if you’re okay with creating as many promises as array elements as is the case in the question:
myArray.reduce(
(p, x) =>
p.then(() => myPromise(x)),
Promise.resolve()
)
but given support, an async function is a better choice. It’s nicely readable and has O(1) instead of O(n) memory overhead.
const forEachSeries = async (iterable, action) => {
for (const x of iterable) {
await action(x)
}
}
forEachSeries(myArray, myPromise)
If you want to collect the return values as an array, that’s:
const mapSeries = async (iterable, fn) => {
const results = []
for (const x of iterable) {
results.push(await fn(x))
}
return results
}
or, without async function support,
const mapSeries = (iterable, fn) => {
const iterator = iterable[Symbol.iterator]()
const results = []
const go = () => {
const {value, done} = iterator.next()
if (done) {
return results
}
return fn(value).then(mapped => {
results.push(mapped)
return go()
})
}
return Promise.resolve().then(go)
}
Runnable snippet:
const myArray = [1, 2, 3, 4, 5, 6]
const sleep = ms =>
new Promise(res => {
setTimeout(res, ms)
})
const myPromise = num =>
sleep(500).then(() => {
console.log('done: ' + num)
})
const forEachSeries = async (iterable, action) => {
for (const x of iterable) {
await action(x)
}
}
forEachSeries(myArray, myPromise)
.then(() => {
console.log('all done!')
})
Upvotes: 151
Reputation: 169
you can use async await
features to run promises sequentially . here's a snippet
async function chainPromiseCalls(asyncFunctions=[],respectiveParams=[]){
for(let i=0;i<asyncFunctions.length;i++){
const eachResult = await asyncFunctions[i](...respectiveParams[i]);
// do what you want to do with each result
}
return ;
}
for parallel you can just call each async function once in a loop , but if you do want to get their combined result , you can use Promise.all
function parallelPromiseCalls(asyncFunctions=[],respectiveParams=[]){
return Promise.all(asyncFunctions.map((func,index)=>func(...respectiveParams[index])))
.then(resultsList=>{
resultsList.forEach((result,index)=>{
// do what you want to do with each result in the list
})
return ;
})
}
note : I am considering respective parameters as an list of lists since multiple parameters should be passed to any one of the function , else if you have to pass only a single parameter to each then you can remove the spread operator.
Upvotes: 7
Reputation: 2541
I know I am very late, and my answer is similar to what others have posted. But I thought I could post a clearer answer which may help any beginner.
Instead of using promises directly, we can use promise factory. Since the promise starts executing as soon as they are created using promise factory we delay the creation of promise.
In this example I create 5 which resolve after a second. I use a promiseCreator to create promises. Now the array promises
uses promiseCreator
to create 5 instances of promises. But array promiseFactories
wraps promiseCreator
in a function so promise is not invoked immediately. It is invoked when used.
Function executeSequentially
executes all promiseLike
sequentially.
promise
array is passed result is promise
array itself executes parallely (actually they executed as soon as they are created, not when this line is called). promiseFactory
array is passed result is new Promise is created when earlier promise has completed their execution.const promiseCreator = (i, time, text) => {
return new Promise(resolve => setTimeout(
() => resolve(console.log(`${i} ${text}`)),
time)
);
}
const promises = [
promiseCreator(1, 1000, "parallel"),
promiseCreator(2, 1000, "parallel"),
promiseCreator(3, 1000, "parallel"),
promiseCreator(4, 1000, "parallel"),
promiseCreator(5, 1000, "parallel"),
]
const promiseFactories = [
() => promiseCreator(1, 1000, "sequential"),
() => promiseCreator(2, 1000, "sequential"),
() => promiseCreator(3, 1000, "sequential"),
() => promiseCreator(4, 1000, "sequential"),
() => promiseCreator(5, 1000, "sequential"),
]
function executeSequentially(promiseLikeArray) {
var result = Promise.resolve();
promiseLikeArray.forEach(function (promiseLike) {
result = result.then(promiseLike);
});
return result;
}
executeSequentially(promises)
executeSequentially(promiseFactories)
Upvotes: 13
Reputation: 10512
You could use Array.reduce
.
//type: [number]
var myArray = [1, 2, 3, 4, 5, 6] //doesn't really matter
//type: number -> Promise<number>
function myPromise(num){
return new Promise((resolve) => {
window.setTimeout(()=>{
resolve(console.log("done: " + num) )
},2000)
})
}
//Array.reduce has type: [a] ~> ((b, a) -> b), b) -> b
//So it can have type:
//[number] ~> ((Promise<number>, number) -> Promise<number>), Promise<number>) -> Promise<number>
//Therefore we need to give reduce a function that takes a Promise
//resolving to a number and a number which makes a new promise.
//This is the function we want:
function sequencePromises(promise, number) {
return new Promise((resolve) => {
resolve(promise.then(_ => myPromise(number)));
});
}
myArray.reduce(sequencePromises, Promise.resolve());
Of course, this simplistic approach won't work if you have a promise which can error, or if you need previous results, so you might want to make sequencePromises
more generic:
function genericSequencePromises(promiseFunction) {
return (promise, parameter) => {
return new Promise((resolve, reject) =>
return promiseFunction(resolve,
reject,
promise,
parameter));
}
}
Then you can do whatever you want as long as you return a Promise.
Finally, you might benefit from this little helper:
function promiseSeries(array, reducer) {
return array.reduce(reducer, Promise.resolve());
}
Bringing it all together:
let sequencePromises = genericSequencePromises((resolve, reject, promise, num) => {
resolve(promise.then(_ => console.log(`done: ${num}`)));
}
promiseSeries(myArray, sequencePromises);
This way, you can not only handle the case in your question, but much more complex cases.
Upvotes: 1
Reputation: 13488
Also you can do it via recursive approach - executeSequentially
calls itself:
function createPromise(x) {
return new Promise(res => {
setTimeout(() => {
console.log(x)
res(x);
}, x * 1000)
})
}
function executeSequentially(array) {
return createPromise(array.shift())
.then(x => array.length == 0 ? x : executeSequentially(array));
}
console.time('executeSequentially');
executeSequentially([1, 2, 3]).then(x => {
console.log('last value: ' + x);
console.timeEnd('executeSequentially');
});
Upvotes: 6
Reputation: 3138
Don't create an array of promises. Create an array of functions returning a promise.
const f = x => new Promise(resolve => setTimeout(() => resolve(console.log(x)), 2000))
(async () => {
for (let job of [1, 2, 3, 4, 5, 6].map(x => () => f(x)))
await job()
})()
Promises start running immediately after creation. Therefore, sequential execution is ensured by constructing the next promise only after finishing the current one.
Upvotes: 29
Reputation: 6377
I would use babel
and do it this way:
let args = [1, 2, 3];
const myPromise = async x => console.log('arg:',x);
const test = async () => {
for (let task of args.map(myPromise))
await task;
}
test().then(console.log('Done'));
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
Upvotes: -1