JavaScript ES6 promise for loop
As you already hinted in your question, your code creates all promises synchronously. Instead they should only be created at the time the preceding one resolves.
Secondly, each promise that is created with new Promise
needs to be resolved with a call to resolve
(or reject
). This should be done when the timer expires. That will trigger any then
callback you would have on that promise. And such a then
callback (or await
) is a necessity in order to implement the chain.
With those ingredients, there are several ways to perform this asynchronous chaining:
With a
for
loop that starts with an immediately resolving promiseWith
Array#reduce
that starts with an immediately resolving promiseWith a function that passes itself as resolution callback
With ECMAScript2017's
async
/await
syntaxWith ECMAScript2020's
for await...of
syntax
But let me first introduce a very useful, generic function.
Promisfying setTimeout
Using setTimeout
is fine, but we actually need a promise that resolves when the timer expires. So let's create such a function: this is called promisifying a function, in this case we will promisify setTimeout
. It will improve the readability of the code, and can be used for all of the above options:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
See a snippet and comments for each of the options below.
1. With for
You can use a for
loop, but you must make sure it doesn't create all promises synchronously. Instead you create an initial immediately resolving promise, and then chain new promises as the previous ones resolve:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i));
}
Values coming back as 'undefined' in for loop inside ES6 promise
The insertItem
does not have to be an async
function. Just return the Promise
and use async
it the promise callback, only in that scope the await
keyword is needed.
An async
function automatically returns a Promise
, but you need a Promise
constructor to use the resolve
and reject
methods. It is basically the same thing but with different syntax and options.
Also I've noticed that your loop has an error. You try to select list.ID
, list.Name
and list.Qty
from the array, but instead should get the values from the items in the array.
I also found a missing "
at the end of your query string.
export const insertItem = (list) => new Promise(async (resolve, reject) => {
try {
list.forEach(item => {
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${item.ID}", "${item.Name}", "${item.Qty}");`;
const response = await db(writeSolutionData, "Inserting Item");
resolve(response);
});
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
Addendum
Updated the answer with usage of Promise.all
. Suggested by @wizloc.
This will loop all of your items and return promises from the db()
function and stores them into a new array. Promise.all
will then resolve whenever all of the promises in the array have fullfilled and return the values of each promise in a single array.
export const insertItem = (list) => new Promise(async (resolve, reject) => {
try {
const responses = list.map(item => {
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${item.ID}", "${item.Name}", "${item.Qty}");`;
return db(writeSolutionData, "Inserting Item");
});
const results = await Promise.all(responses);
resolve(results);
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
How to return many Promises and wait for them all before doing other stuff
You can use Promise.all
(spec, MDN) for that: It accepts a bunch of individual promises and gives you back a single promise that is resolved when all of the ones you gave it are resolved, or rejected when any of them is rejected.
So if you make doSomeAsyncStuff
return a promise, then:
const promises = [];
// ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
for (let i = 0; i < 5; i++) {
// ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
promises.push(doSomeAsyncStuff());
}
Promise.all(promises)
.then(() => {
for (let i = 0; i < 5; i++) {
// ^^^−−−−−−−−−−−−−−−− added missing declaration
doSomeStuffOnlyWhenTheAsyncStuffIsFinish();
}
})
.catch((e) => {
// handle errors here
});
MDN has an article on promises here. I also cover promsies in detail in Chapter 8 of my book JavaScript: The New Toys, links in my profile if you're interested.
Here's an example:
function doSomethingAsync(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Resolving " + value);
resolve(value);
}, Math.floor(Math.random() * 1000));
});
}
function test() {
const promises = [];
for (let i = 0; i < 5; ++i) {
promises.push(doSomethingAsync(i));
}
Promise.all(promises)
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
// Handle errors here
});
}
test();
How to create a loop of promises
If you want to loop and serialise the promises, not executing any other get
calls once one fails, then try this loop:
async function getAllResults() { // returns a promise for 250 results
let totalResults = [];
try {
for (let i = 0; i < 5; i++) {
totalResults.push(...await getResults.get());
}
} catch(e) {};
return totalResults;
}
This uses the EcmaScript2017 async
and await
syntax. When not available, chain the promises with then
:
function getAllResults() {
let totalResults = [];
let prom = Promise.resolve([]);
for (let i = 0; i < 5; i++) {
prom = prom.then(results => {
totalResults = totalResults.concat(results);
return getResults.get();
});
}
return prom.then(results => totalResults.concat(results));
}
Note that you should avoid the promise construction anti-pattern. It is not necessary to use new Promise
here.
Also consider adding a .catch()
call on the promise returned by the above function, to deal with error conditions.
Finally, be aware that concat
does not modify the array you call it on. It returns the concatenated array, so you need to assign that return value. In your code you don't assign the return value, so the call has no effect.
See also JavaScript ES6 promise for loop.
How to use loop in javascript promises chain
This is broadly how this would work.
If this function encrypts a single ingredient:
function encryptIngredient(ingredient) {
return new Promise(...);
}
Then this function encrypts a list of ingredients:
function encryptIngredients(ingredients) {
// Note that 'shift' changes the array
const current = ingredients.shift();
return encryptIngredient(current).then( () => {
if (ingredients.length > 0) {
return encryptIngredients(ingredients);
}
});
}
Here's the async/await version of the last function. It's much simpler:
async function encryptIngredients(ingredients) {
for(const ingredient of ingredients) {
await encryptIngredient(ingredient);
}
}
Javascript: how to create an infinite-loop of promises?
If you're going to use a loop, then you will have to use await
because otherwise, your loop will just run forever and never give the asynchronous operation any cycles to actually process its completion:
while (true) {
result = await asyncOperation()
if (good result) {
break;
}
}
This, of course, needs to be inside a function declared as async
.
Before await
, the usual way to do this would be with a local function and a recursive-looking structure (that by the way does not have any stack build-up from recursion because of the async operation):
function run() {
return asyncOperation().then(result => {
if (good result) {
return result;
} else {
// try again
return run();
}
});
}
run().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
And, typically you would insert some small delay before calling the asyncOperation()
again in order to avoid hammering the target server. And, you would have either a timeout or a max number of retries to avoid a situation where this loop runs forever.
javascript - working with multiple promises inside loop - how to return data outside of promise?
You need two Promise.all
s - one to iterate over each student, and a nested one to fetch the getMealBooking
and getStudentData
for each student.
Put everything into an async function (that catches and sends false
if needed) to make the control flow easier to understand.
const { docs } = await db_sh.find({
selector: { sh: req.params.sh_id },
fields: ['_id', 'sh_id', 'time'],
sort: ['_id']
});
if (docs.length === 0) {
// no data; stop here
res.send({});
return;
};
const data = {};
await Promise.all(
docs.map(student => (
Promise.all([getMealBooking(student._id), getStudentData(student._id)])
.then(([mealBookingData, studentData]) => {
data[student.time] = [mealBookingData, studentData];
})
))
);
const sortedData = Object.keys(data).sort().reduce((a, c) => (a[c] = data[c], a), {});
res.send(sortedData);
Related Topics
Sending Command Line Arguments to Npm Script
What Is the JavaScript ≫≫≫ Operator and How to Use It
How to Chain and Share Prior Results With Promises
JavaScript and Operator Within Assignment
What Are Alternatives to Document.Write
Curly Brackets in Arrow Functions
How to Make Jquery UI Tabs Scroll Horizontally If There Are Too Many Tabs
Is It Not Possible to Stringify an Error Using Json.Stringify
Simulate Background-Size:Cover on <Video> or <Img>
How to Get Just Numeric Part of CSS Property with Jquery
How to Detect CSS3 Resize Events