Cleaner Promise.all syntax? - javascript

I am fairly new to Node.JS, and I really hate the syntax of Promise.all returning an array.
eg.
const requiredData = await Promise.all([
getFirst(city),
getSecond(hubIds),
getThird(city, customerCategoryKey),
getFourth(request)
])
const firstData = requiredData[0];
const secondData = requiredData[1];
const thirdData = requiredData[2];
const fourthData = requiredData[3];
I need to individually fetch them in separate lines of code.
Isn't there a way like
const {
firstData,
secondData,
thirdData,
fourthData
} = await Promise.all([
getFirst(city),
getSecond(hubIds),
getThird(city, customerCategoryKey),
getFourth(request)
])
Basically, I'd really like if there is a cleaner way than the first code snippet.
TIA!

As mentioned in the comments you can use Array destructor instead of using Object destructor:
(async () => {
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
// get all elements as variables
const [p1, p2, p3] = await Promise.all([promise1, promise2, promise3]);
console.log(p1, p2, p3);
})();

In case it's not obvious, if you're okay running the promises in serial order, you can await them inline -
const main = async () =>
{ const a = await mock("one")
const b = await mock("two")
const c = await mock("three")
const d = await mock("four")
console.log(a, b, c, d)
}
// test funcs
const rand = n =>
Math.floor(Math.random() * n)
const mock = (x) =>
new Promise(r => setTimeout(r, rand(1000), x))
// run
console.log("loading...")
main().catch(console.error)
// loading...
// one two three four
If you want to run the promises in parallel but retrieve assign the values by name, we could fromDescriptor which does the wiring for us -
const fromDescriptor = (desc = {}) =>
Promise.all(
Object
.entries(desc)
.map(([ k, px ]) => px.then(x => [ k, x ]))
)
.then(Object.fromEntries)
const main = async () =>
{ const init =
{ a: mock("one")
, b: mock("two")
, c: mock("three")
, d: mock("four")
}
const { a, b, c, d } =
await fromDescriptor(init)
console.log(a, b, c, d)
}
// test funcs
const rand = n =>
Math.floor(Math.random() * n)
const mock = (x) =>
new Promise(r => setTimeout(r, rand(1000), x))
// run
console.log("loading...")
main().catch(console.error)
// loading...
// one two three four

Related

how to do Sequential API Calls in javascript? [duplicate]

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?
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!')
})
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.
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.
When 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).
When 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)
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');
});
Sequential:
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 ;
}
Parallel:
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.
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.
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),
}),
{}
);
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/babel-standalone#6.24.0/babel.min.js"></script>
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))
)

How can I run these js function in row (node.js)? [duplicate]

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?
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!')
})
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.
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.
When 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).
When 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)
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');
});
Sequential:
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 ;
}
Parallel:
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.
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.
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),
}),
{}
);
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/babel-standalone#6.24.0/babel.min.js"></script>
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))
)

JavaScript - Promise.allSettled + Array.reduce()

Introduction
Imagine this method for getting the language of a user:
const getUserLanguage = (userId) => new Promise(
(resolve, reject) => {
if (Math.random() < 0.3) resolve("en");
if (Math.random() < 0.6) resolve("es");
reject("Unexpected error.");
}
);
(async () => {
try {
const language = await getUserLanguage("Mike")
console.log(`Language: ${language}`);
} catch(err) {
console.error(err);
}
})();
Now, I am trying to group the language of multiple users, performing a parallel request:
const getUserLanguage = () => new Promise(
(resolve, reject) => {
if (Math.random() < 0.3) resolve("en");
if (Math.random() < 0.6) resolve("es");
reject("Unexpected error.");
}
);
const groupUsersByLanguage = async (userIds) => {
const promiseResults = await Promise.allSettled(
userIds.reduce(async (acc, userId) => {
const language = await getUserLanguage(userId);
(acc[language] = acc[language] ?? []).push(userId);
return acc;
}, {})
);
console.log({ promiseResults });
// Filter fulfilled promises
const result = promiseResults
.filter(({ status }) => status === "fulfilled")
.map(({ value }) => value);
return result;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
Problem
But my implementation is not working:
const promiseResults = await Promise.allSettled(
userIds.reduce(async (acc, userId) => {
const language = await getUserLanguage(userId);
(acc[language] = acc[language] ?? []).push(userId);
return acc;
}, {})
);
How can I do for getting an output like
{
"es": ["Mike", "Saul"],
"en": ["Walter"],
}
using the Promise.allSettled combined with .reduce?
Your .reduce is constructing an object where each value is a Promise. Such an object is not something that .allSettled can understand - you must pass it an array.
I'd create an object outside, which gets mutated inside a .map callback. This way, you'll have an array of Promises that .allSettled can work with, and also have the object in the desired shape.
const getLanguage = () => new Promise(
(resolve, reject) => {
if (Math.random() < 0.3) resolve("en");
if (Math.random() < 0.6) resolve("es");
reject("Unexpected error.");
}
);
const groupUsersByLanguage = async (userIds) => {
const grouped = {};
await Promise.allSettled(
userIds.map(async (userId) => {
const language = await getLanguage(userId);
(grouped[language] = grouped[language] ?? []).push(userId);
})
);
return grouped;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
An option that doesn't rely on side-effects inside a .map would be to instead return both the userId and the language inside the map callback, then filter the allSettled results to include only the good ones, then turn it into an object.
const getLanguage = () => new Promise(
(resolve, reject) => {
if (Math.random() < 0.3) resolve("en");
if (Math.random() < 0.6) resolve("es");
reject("Unexpected error.");
}
);
const groupUsersByLanguage = async (userIds) => {
const settledResults = await Promise.allSettled(
userIds.map(async (userId) => {
const language = await getLanguage(userId);
return [userId, language];
})
);
const grouped = {};
settledResults
.filter(result => result.status === 'fulfilled')
.map(result => result.value)
.forEach(([userId, language]) => {
(grouped[language] = grouped[language] ?? []).push(userId);
});
return grouped;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
I would write a main function using two utility functions for this: one that groups a set of elements according to the result of a function, and one that takes a predicate function and partitions an array into those ones for which it returns true and those ones for which it returns false. These two in turn use a push utility function which simply reifies Array.prototype.push into a plain function.
The main function maps the getUserLanguage function over the users, calls Promise.allSettled on the results, then we map over the resulting promises, to connect the original userId back with the promise results. (If the fake getUserLanguage returned an object with properties for both the userId and language, this step would be unnecessary.) Then we partition the resulting promises to separate out the fulfilled from the rejected ones. I do this because your question doesn't say what to do with the rejected language lookups. I choose to add one more entry to the output. Here as well as es and en, we also get a list of userIds under _errors. If we wanted to ignore these, then we could replace the partition with a filter and simplify the last step. That last step takes successful results and the failures, combining the successful ones into an object with our group helper, and appending the _errors, by mapping the failures to their userIds.
It might look like this:
// dummy implementation, resolving to random language, or rejecting with error
const getUserLanguage = (userId) => new Promise ((resolve, reject) => {if (Math.random() < 0.3) resolve("en"); if (Math.random() < 0.6) resolve("es"); reject("Unexpected error.");});
// utility functions
const push = (x) => (xs) =>
(xs .push (x), xs)
const partition = (fn) => (xs) =>
xs .reduce (([y, n], x) => fn (x) ? [push (x) (y), n] : [y, push (x) (n)], [[], []])
const group = (getKey, getValue) => (xs) =>
xs .reduce ((a, x, _, __, key = getKey (x)) => ((a [key] = push (getValue (x)) (a[key] ?? [])), a), {})
// main function
const groupUsersByLanguage = (users) => Promise .allSettled (users .map (getUserLanguage))
.then (ps => ps .map ((p, i) => ({...p, user: users [i]})))
.then (partition (p => p .status == 'fulfilled'))
.then (([fulfilled, rejected]) => ({
...group (x => x .value, x => x.user) (fulfilled),
_errors: rejected .map (r => r .user)
}))
// sample data
const users = ['fred', 'wilma', 'betty', 'barney', 'pebbles', 'bambam', 'yogi', 'booboo']
// demo
groupUsersByLanguage (users)
.then (console .log)
.as-console-wrapper {max-height: 100% !important; top: 0}
This yields output like this (YMMV because of the random calls):
{
en: [
"fred",
"wilma",
"barney"
],
es: [
"bambam",
"yogi",
"booboo"
],
_errors: [
"betty",
"pebbles"
]
}
Note that those utility functions are general-purpose. If we keep our own libraries of such tools handy, we can write functions like this without great effort.
Another option of doing this would be to first fetch all languages using:
const languages = await Promise.allSettled(userIds.map(getLanguage));
Then zip then together with userIds and process them further.
async function getLanguage() {
if (Math.random() < 0.3) return "en";
if (Math.random() < 0.6) return "es";
throw "Unexpected error.";
}
function zip(...arrays) {
if (!arrays[0]) return;
return arrays[0].map((_, i) => arrays.map(array => array[i]));
}
async function groupUsersByLanguage(userIds) {
const languages = await Promise.allSettled(userIds.map(getLanguage));
const groups = {};
for (const [userId, language] of zip(userIds, languages)) {
if (language.status != "fulfilled") continue;
groups[language.value] ||= [];
groups[language.value].push(userId);
}
return groups;
}
(async () => {
const userIds = ["Mike", "Walter", "Saul", "Pinkman"];
const usersGroupedByLanguage = await groupUsersByLanguage(userIds);
console.log(usersGroupedByLanguage);
})();
If you are not interested in creating a zip() helper you can use a "normal" for-loop:
const groups = {};
for (let i = 0; i < userIds.length; i += 1) {
if (languages[i].status != "fulfilled") continue;
groups[languages[i].value] ||= [];
groups[languages[i].value].push(userId);
}

Sequential function calls

I have an array of asynchronous functions, it is necessary to call in order and the result of the call of the previous function is passed into the arguments. How can this be done approximately?
// lets say we have a function that takes a value and adds 20 to it asynchronously
const asyncPlus20 = num => Promise.resolve(num+a)
const arr = [asyncPlus20, asyncPlus20]
let res = 0 // some starting value
for (const f of arr) res = await f(res)
// res is now 20
One of the best way for Array of Async functions is to use For...of.
Run the below snippet in the console. >> Also includes the argument passing
const twoSecondsPromise = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('2000_'), 2000);
})
};
const threeSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '3000_'), 3000);
})
};
const fiveSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '5000_'), 5000);
})
};
(async function () {
const asyncFunctions = [twoSecondsPromise, threeSecondsPromise, fiveSecondsPromise];
let result;
for (const file of asyncFunctions) {
result = await file(result);
console.log(result);
}
})();

How to execute promises sequentially, passing the parameters from an array?

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?
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!')
})
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.
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.
When 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).
When 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)
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');
});
Sequential:
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 ;
}
Parallel:
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.
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.
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),
}),
{}
);
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/babel-standalone#6.24.0/babel.min.js"></script>
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))
)

Categories

Resources