JavaScript Es6 Promise for Loop

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:

  1. With a for loop that starts with an immediately resolving promise

  2. With Array#reduce that starts with an immediately resolving promise

  3. With a function that passes itself as resolution callback

  4. With ECMAScript2017's async / await syntax

  5. With 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.alls - 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



Leave a reply



Submit