Dynamic sequential execution of promises - javascript

I have a dynamic number of promises that I need to run sequentially.
I understood how I can run sequentially promises but I don't succeed to make it dynamic with a number of promises that could vary.
Here is a way I found to do it statically How to resolve promises one after another? :
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
waitFor(1000).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(2000);
}).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(3000);
}).then(function(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="result"></div>
I would like to do the same but instead of 3 nested promises, I would like to have any number I want.
Can you help me ?
Thanks a lot!!

There are three basic ways to achieve this task with Promises.
.reduce() pattern.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
sequence = tos => tos.reduce((p,c) => p.then(rp => waitFor(c))
.then(rc => console.log(`${rc} # ${new Date().getSeconds()}`)), Promise.resolve());
sequence(timeouts);
The recursive pattern.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
sequence = ([to,...tos]) => to !== void 0 && waitFor(to).then(v => (console.log(`${v} # ${new Date().getSeconds()}`), sequence(tos)));
sequence(timeouts);
Scan from left pattern.
The scanl pattern would sequence promises one after another but once it is completed you also have access to the interim promise resolutions. This might be useful in some cases. If you are going to construct an asynchronous tree structure lazily (branching from the nodes only when needed) you need to have access to the previous promise resolutions.
In order to achieve scanl functionality in JS, first we have to implement it.
var scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc))
we feed scanl with xs which is the array of timeouts in this particular example, f which is a callback function that takes acc (the accumulator) and e (current item) and returns the new accumulator. Accumulator values (the interim promise resolutions) are mapped over the timeouts array to be accessed when needed.
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
var timeouts = [1000, 2000, 2000, 3000, 1000],
scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc)),
proms = scanl(timeouts, // input array
(a,t,r) => a.then(v => (r = v, waitFor(t))) // callback function
.then(v => (console.log(`${r} and ${v}`),
`${r} and ${v}`)),
Promise.resolve(`Started with 0`)); // accumulator initial value
// Accessing the previous sub sequential resolutions
Promise.all(proms)
.then(vs => vs.forEach(v => console.log(v)));
.as-console-wrapper {
max-height: 100% !important
}

Make a seprate function to handle the number of iterations
function waitFor(timeout) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(`Finished waiting ${timeout} milliseconds`);
}, timeout);
});
}
function resultHandler(result) {
$('#result').append(result+' # '+(new Date().getSeconds())+'<br>');
return waitFor(2000);
}
function repeat(promise,num){
if(num>0)
repeat(promise.then(resultHandler),num-1);
}
repeat(waitFor(1000),2)

Forget I commented (when you convert a Promise to Observable or include the promise in an array, the Promise is executed). You can use a "recursive" function
foolPromise(index: number, interval: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: index, data: new Date().getTime() % 100000 });
}, interval);
})
}
intervals: number[] = [1000, 50, 500];
recursive(req: any, index: number) {
req.then(res => {
console.log(res);
index++;
if (index < this.intervals.length)
this.recursive(this.foolPromise(index, this.intervals[index]), index);
})
}
ngOnInit() {
this.recursive(this.foolPromise(0, this.intervals[0]), 0)
}

If you don't care for serialization, you can use Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all([promise1, promise2, promise3]).then(function(values) {
// do something with values
}).catch(function(err) {
// error called on first failed promise
});
Or, you can use an async function:
async function doSomething(arrayOfPromises) {
for (const item of arrayOfPromises) {
const result = await item;
// now do something with result
}
};

Related

Parallel rxJs Observables without waiting for all to complete

Is there a way to request many similar requests in parallel using rxJs Observables in parallel without waiting for all requests to complete. I want to handle as each one comes. I don't want to use a loop of requests. Use case is one of the request is slow and there are many of them
I tried forkJoin (below) but it waits for all to complete. and mergeMap is not taking an array. Any help? Thanks
/*
// example of many posts
const post1 = getPostHTTP(1);
const post2 = getPostHTTP(2); // A slow post
const post3 = getPostHTTP(3);
...
*/
const promises = [];
for (let i = 0; i < 100; i++) {
promises.push(getPostHTTP(i))
}
forkJoin(promises).pipe(
finalize(() => {
//displayPost
})).subscribe(posts => {
posts.forEach((post: any) => {
displayPost(post)
})
})
I think that a mix of from and mergeMap is what you are looking for. The code could look like this
for (let i = 0; i < 100; i++) {
promises.push(getPostHTTP(i))
}
from(promises).pipe(
mergeMap(post => displayPost(post)),
).subscribe(
next: result => {// do something with the result of each displayPost operation},
error: err => {// handle errors},
complete: () => {// do something when everything is completed}
)
You should convert Promise to Observable via from (https://www.learnrxjs.io/learn-rxjs/operators/creation/from) and merge them into one Observable via merge (https://www.learnrxjs.io/learn-rxjs/operators/combination/merge).
It will be like:
merge(...promises.map(p => from(p))
.subscribe(post => {
// do smth with post
});
You can use Promise API to send multiple requests simultaneously and await them all to resolve.
If you have a condition that you need to run those promises sequentially, then this solution might not work for you.
Excerpt is from MDN.
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, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
reject('reject');
});
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
//From console:
//"reject"
//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}).catch(reason => {
console.log(reason)
});

sequential and parallel processing of promises

The idea is to sequentially iterate the array but parallel process each item in the subarray.
Once record #1 is processed in parallel then it moves to the record #2 and parallel process it's items and so on. So basically it's a combination of sequentiality and parallelism.
Concat all results in a single dimension array and display. (pending)
If input contains an array of arrays.
var items = [
["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]
]
And an action that processes these items.
function action(item) {
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(item + ":processed");
}, 100)
});
}
Attempt
describe("", function(){
this.timeout(0);
it("should", function(done){
items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(resolve, reject);
});
});
}, Promise.resolve())
});
});
Expectations:
Ideally a clean minimalistic and a functional approach (no state) to return the results to the caller.
Attempt 2
var chain = items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(resolve, reject);
});
});
}, Promise.resolve());
chain.then(console.log, console.error); // I need all results here
displays last result only. [ 'item9:processed', 'item10:processed' ]
Edit final solution based on the answer.
var chain = items.reduce(function(accumulator, currentValue, currentIndex, array){
return accumulator.then(function(result){
return new Promise(function(resolve, reject){
Promise.all(currentValue.map(action))
.then(function(data){
resolve(result.concat(data)) // new array
}, reject);
});
});
}, Promise.resolve([]));
chain.then(console.log, console.error);
One way to do this:
var items = [
["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]
]
function action(item) {
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(item + ":processed");
}, 100)
});
}
function process(items) {
return items.reduce((m, d) => {
const promises = d.map(i => action(i));
let oldData;
return m.then((data) => {
oldData = data;
return Promise.all(promises);
})
.then(values => {
//oldData.push(...values);
oldData.push.apply(oldData, values);
return Promise.resolve(oldData);
})
}, Promise.resolve([]))
}
process(items).then(d => console.log(d))
//Prints:
// ["item1:processed","item2:processed","item3:processed","item4:processed","item5:processed","item6:processed","item7:processed","item8:processed","item9:processed","item10:processed"]
A simple functional way of doing this would be like
map sub array with parallel data by promise returning async functions
Promise.all() the resulting promises array
sequence the sub arrays at the .then() stage of Promise.all() in a recursive fashion
ES6; tools like spread syntax / rest parameters and array destructuring are very handy for this job.
var items = [["item1", "item2"],
["item3", "item4"],
["item5", "item6"],
["item7", "item8"],
["item9", "item10"]],
act = i => new Promise(v => setTimeout(v, 1000, `${i}: processed`)),
seqps = ([is,...iss]) => is && Promise.all(is.map(i => act(i)))
.then(([p,q]) => (console.log(`${p} and ${q}`),
seqps(iss)));
seqps(items);

How to dynamically add new promise to the promises chain

I want to create promises chain and then dynamically add as many promises to it as it's needed. These additions could be in some cycle with dynamic number of steps so that I can't use chain like .then().then().then... Code bellow works improperly but you'll get the idea. Result should be a console logged 3000, 4000, 5000 numbers in 3, 4 and 5 seconds consequently but actually doesn't work that way. Any ideas?
let launchChain = function(delay)
{
return new Promise((resolve: Function, reject: Function) => {
setTimeout(() => {
console.log(delay);
resolve();
}, delay)
})
}
let chain = launchChain(3000);
chain.then(function () {
return launchChain(4000);
})
chain.then(function () {
return launchChain(5000);
})
So used reduce and this site
var delays = [0, 1000, 2000, 3000, 4000];
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function() {
return launchChain(item);
});
// uses this orignal promise to start the chaining.
}, Promise.resolve());
}
function launchChain(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(delay);
resolve();
}, delay);
});
}
workMyCollection(delays);
[EDIT] : Another way
var delays = [0, 1000, 2000, 3000, 4000];
var currentPromise = Promise.resolve();
for (let i = 0; i < delays.length; i++) {
// let makes i block scope .. var would not have done that
currentPromise = currentPromise.then(function() {
return launchChain(delays[i]);
});
}
function launchChain(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(delay);
resolve();
}, delay);
});
}
Do let me know if this worked for you :)
thanks to this question I learned a lot!
function run(delay){
let chain = launchChain(delay);
chain.then(function() {
run(delay+1000);
});
}
run(3000);
Thanks sinhavartika! It works! But I actually took example from here
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
and changed it a bit and now I use it in my project in the following way:
/**
* Runs promises from promise array in chained manner
*
* #param {array} arr - promise arr
* #return {Object} promise object
*/
function runPromiseInSequense(arr) {
return arr.reduce((promiseChain, currentPromise) => {
return promiseChain.then((chainedResult) => {
return currentPromise(chainedResult)
.then((res) => res)
})
}, Promise.resolve());
}
var promiseArr = [];
function addToChain(delay)
{
promiseArr.push(function (delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(delay);
resolve();
}, delay)
});
}.bind(this, delay))
}
addToChain(1000);
addToChain(2000);
addToChain(3000);
addToChain(4000);
runPromiseInSequense(promiseArr);

Why does async array map return promises, instead of values

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.

Promise in sequence does not stop when a promise reject

I have a list of promises.
var p1 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise 1 done");
}, 2000);
})
}
};
var p2 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("Promise 2 reject");
}, 1000);
})
}
};
var p3 = {
run: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise 3 done");
}, 1500);
})
}
};
I want to execute [p1,p2,p3] in sequence. I writed a function Process.sequence to act like Promise.all() (resolve when all promises resolve, and reject right after a promise rejects)
Process = {
sequence: function(promises){
window.promises = promises;
return new Promise(function (resolve, reject) {
promises.reduce(function (sequence, promise) {
return sequence.then(function () {
return promise.run();
}).then(function (result) {
console.log(result);
if (promises.indexOf(promise) == promises.length - 1) {
resolve("All Done");
}
}).catch(function (reason) {
reject(reason);
});
}, Promise.resolve());
});
}
};
But when i run Process.sequence...
Process.sequence([p1,p2,p3]).then(function(result){
console.log(result);
}, function(reason){
console.log(reason);
});
... the p3 still executed even p2 had rejected before.
Here is the result i expect:
Promise 1 done
Promise 2 reject
But this is the real result:
Promise 1 done
Promise 2 reject
Promise 3 done
What wrong with my function Process.sequence?
UPDATE
Thank #JaromandaX for your support. The function Process.sequence should be like this.
Process = {
sequence: function(promises) {
return promises.reduce(function(sequence, promise) {
return sequence.then(function() {
return promise.run();
}).then(function(result) {
console.log(result);
});
}, Promise.resolve()).then(function() {
return "All Done";
});
}
};
As you want the results to contain all of the fulfilled values, and the promises only to be created ("run") when all previous ones were fulfilled, you should make some changes:
Make your loop asynchronous, as you can only know whether to continue with the next promise or not when the previous one has resolved.
Stop looping when a promise rejects
Concatenate the results in an array as you progress
Furthermore, I would not call a variable "promise" when it is not a promise object... that will only bring confusion. Call it task or something. The promise here is what the task.run() method returns.
Here is how I would suggest to do it:
// The p1, p2, p3 objects have been written more concisely using a helper function:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };
const Process = {
sequence: function (tasks) {
return (function loop(results) {
return results.length >= tasks.length
// All promises were fulfilled: return the results via a promise
? Promise.resolve(results)
// Create the next promise
: tasks[results.length].run()
// When it fulfills, collect the result, and chain the next promise
.then(value => loop(results.concat(value)))
// When it rejects, return a rejected promise with
// the partial results and the reason of rejection
.catch(reason => { throw results.concat(reason) });
})([]); // Start with an empty results array
}
};
console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
console.log('Fulfilled: ', result);
}).catch(function(reason){
console.log('Rejected: ', reason);
});
As browsers have started to support async/await you could also use this more procedural looking code:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const p1 = { run: _ => wait(2000).then(_ => "Promise 1 fulfilled") };
const p2 = { run: _ => wait(1000).then(_ => { throw "Promise 2 rejected" }) };
const p3 = { run: _ => wait(1500).then(_ => "Promise 3 fulfilled") };
const Process = {
sequence: async function (tasks) {
const results = [];
for (let task of tasks) {
try {
results.push(await task.run());
} catch(err) {
throw results.concat(err);
}
}
return results;
}
};
console.log('Wait for the results to come in...');
Process.sequence([p1, p2, p3]).then(function(result){
console.log('Fulfilled: ', result);
}).catch(function(reason){
console.log('Rejected: ', reason);
});

Categories