How do I know that the loop callback is complete? - javascript

allDoc.map(function(stage){
for(let key of stage.layerItem){
//key.toImage is a library, so it only supports callback.
key.toImage({
callback(img) {
addPhoto(key.attrs)
}
});
}
})
// Run when addPhoto is done
console.log("done")
async function addPhoto(params){
const stored = await s3.upload(params).promise()
return stored.Location
}
When the addPhoto function is completed, how do I proceed to the next code?

First wrap the toImage function in one that returns a promise then use Promise.all to wait for them all to settle.
(Community Wiki because this is close to being a duplicate question but has two distinct steps each of which has their own duplicate).

One of the solutions I found for waiting .map() to be complete, is putting a Promise.all().
Your code should look like this:
const promisse = allDoc.map(function(stage, index){
// Your loop
})
await Promise.all(promisse);
console.log('done');
Keep in mind that if your allDoc.map is inside another function, the upper function must be async.

Related

Instruction not waiting properly NodeJS [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 1 year ago.
I was wondering what is the problem here, why is the return statement not waiting to finish the previous instruction? orders are empty, even though I'm logging inside the getOrders to make sure there were data (there is data).
await stores.forEach(async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
return orders;
To wait for all promises, you need an array of promises, which you can get using map. Then you need to use Promise.all, so that it will wait for all the promises in that array to complete.
const orders = await Promise.all(
stores.map(store => getOrders(store, token, tokenType))
);
return orders;
*Edit: rewritten and simplified based on suggestion from #PatrickRoberts
To paraphrase his comment, in the old code when using a separate array with an array.push, the array ordering would be indeterminate. However using the array of results from Promise.all will always match the order of the original array.
It's a common misunderstanding that asynchronous callbacks (declared with async and called with await) passed to forEach or map will be executed synchronously or in order. Actually, they are not.
When you check the polyfill of those methods, you'll find a line like this callback.call(T, kValue, k, O);. So, basically, it just execute the callback. If the callback is an asynchronous method, it doesn't wait its execution to be done. Instead, it continue executing other codes.
Let's set an example using your code, let' say this is the callback:
const callback = async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
When it was passed to forEach:
stores.forEach(callback);
basically, what forEach does is iterating the array, and call callback on each elements. May looks like this
...
loop {
callback.call(...)
}
...
There is no wait here. However, each time, the code inside your callback is executed synchronously since it's declared with async and called with await, which means after getOrders is resolved and the result is pushed to orders.
So, if you want to execute the callbacks synchronously and in order, you may write a generic for loop.
async function addOrders(){
for(let store of stores) {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
}
}
addOrders()

Repeatedly call a function that returns a promise until one of them contains a specific response

Hello I need to call a REST function with an ID, that returns a promise in React.js. This function will at some point contain a certain value in its response when called . Until another service has processed an initial request this value will be null.
This is what I have done so far:
while(myVariable){
myfunction(myID).then( (response) => {
if(response['value'] != null
myVariable = false;
}
});
}
The problem with this code is that the while loop is called as fast as possible and thus completely utilises the computer. So I need a function that allows me to poll for a result by an ID until the response of one of the function calls contains a valid response.
I have tried the following method but without success, because I don't have a fixed number of runs:
Wait promise inside for loop
Thanks in regards.
As you state, the problem is that the while loop runs eagerly, not waiting for each promise to resolve.
One way to solve that is to use recursion. Recursion gives you more control over when exactly you want to 'loop' next:
let getValue = () => {
myFunction(myID).then(response => {
if (response['value'] === null) {
setTimeout(getValue);
} else {
// here you know the other service has processed the initial request
}
});
};
First I wrapped the whole thing in a function called getValue. Note that this function is only called again after the promise resolves. (The call to setTimeout is a trick to use recursion without consuming the stack.) If this still runs too quickly, pass an additional parameter of 100 or so to the setTimeout invocation.
Alternatively, you may be able to use async/await similarly to the link you shared. I'm no expert on async/await but it should work the same with while loops as with for loops, judging by this and this.
You can use the async function with await.
I also use a delay function to delay each call to the myfunction().
While you get a response, you can break the while loop.
const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
async function main() {
const myID = 1;
let response;
while (true) {
response = await myfunction(myID);
if (response["value"] != null) {
break;
}
await delay(5000);
}
//do Something once you get the response here below:
}
main();

Is Array.forEach asynchronous?

According to JavaScript, Node.js: is Array.forEach asynchronous?, Array.forEach is synchronous. However, for my code below:
function wait5() {
return new Promise(resolve =>
setTimeout(resolve, 5000));
}
async function main() {
console.log("Start");
[1,2].forEach(async (e) => {
const d = await wait5().then(()=> console.log("5s later") )
})
console.log("This should come last!");
}
main();
The output is:
Start
This should come last!
5s later
5s later
with the two "5s later" coming out in rapid succession.
Why is this the case?
If I use a normal for loop:
async function main() {
console.log("Start");
for (let i=0;i<2;i++) {
const d = await wait5().then(()=> console.log("5s later") )
}
console.log("This should come last!");
}
then the result is what I wanted:
Start
5s later
5s later
This should come last!
forEach is synchronous. Your particular callback function, however, is not. So forEach synchronously calls your function, which starts its work, once for each entry in the array. Later, the work that started finishes asynchronously, long after forEach has returned.
The issue is that your callback is async, not that forEach is asynchronous.
In general, when you're using an API like forEach that doesn't do anything with the return value (or doesn't expect a promise as a return value), either:
Don't pass it an async function, or
Ensure that you handle errors within the function itself
Otherwise, you'll get unhandled errors if something goes wrong in the function.
Or of course:
Use a try/catch block within the async function to catch and handle/report errors within the function itself.
It looks like you're declaring an async function inside of a caller that really doesn't care for that sort of thing, forEach. Declaring a function async makes it promise-like, but that promise is only useful if acted on.
If you need a promise-aware forEach, that's something you could implement, though Promise.each in libraries like Bluebird already do that.

Synchronous loop in Promise all

I would like to do a synchronous loop in a part of my code.
The function saveInDatabase checks if the item title (string) already exists in the database. That's why it can't be resolved in parallel, otherwise the condition will never apply (and would create duplicates).
Promise.all(arr.map(item => {
saveInDatabase(item).then((myResult) => ... );
}));
I tried to encapsulate this function into separate promises, also tried with npm packages (synchronous.js, sync), but it seems that it does not fit with my code.
Maybe this solution is completely silly.
Do you think it's a better idea to replace promise.all by a synchronous loop (foreach for example) ? The problem is that I need the results of each iteration...
I'm using Node 6.11.2. Could you give me some tips to handle that ?
Thank you in advance.
Without using await (which is not in node.js v6.11.2, but would make this simpler), a classic pattern for serializing a bunch of async operations that return a promise is to use a reduce() loop like this:
arr.reduce(function(p, item) {
return p.then(function() {
return saveInDatabase(item).then((myResult) => ... );
});
}, Promise.resolve()).then(function() {
// all done here
}).catch(function(err) {
// error here
});
If you want to save all the results, you can use your .then(myResult => ...) handler to .push() the result into an array which you can access when done.
This will serialize all the calls to saveInDatabase(item) to it waits for the first one to be done before calling the second one, waits for the second one to be done before calling the third one, etc...
The default implementation here will stop if saveInDatabase(item) rejects. If you want to keep going (you don't say in your question), even when it gives an error, then you can add a .catch() to it to turn the rejected promise into a fulfilled promise.
In node.js v7+, you can use await in a regular for loop:
async function myFunc() {
let results = [];
for (let item of arr) {
let r = await saveInDatabase(item).then((myResult) => ... );
results.push(r);
}
return results;
}
myFunc().then(function(results) {
// all done here
}).catch(function(err) {
// error here
});
If you could run all the requests in parallel, then you could do that like this:
Promise.all(arr.map(item => {
return saveInDatabase(item).then((myResult) => ... );
})).then(function(results) {
// all done here
}).catch(function(err) {
// error here
});
In any of these, if you don't want it to stop upon a rejection, then add a .catch() to your saveInDatabase() promise chain to turn the rejection into a resolved promise with some known value or error value you can detect.

nodejs looping through array with async calls?

I am trying to iterate through an array which pushes a new Thing to a list, inside the Thing it does some async calls of its own. How would I iterate through an array in a synchronous way, as the callback requires the the data from the list to work. Since my for loop is synchronous and does some asynchronous calls, the callback is called before the the list if finished being made.
I do not know how I would iterate through the array and do all the work before doing the callback
load(file, callback) {
fs.readFile(file, (err, fd) => {
var data = JSON.parse(fd);
for(var i of data.array){
this.list.push(new Thing(i.id)); // new Thing is Asynchronous
}
callback(); // Needs a finished list
return;
});
}
Solved it:
By converting my Thing class to synchronous, by removing the asynchronous calls to a function inside the class, and first instantiating all the Things in the loop then calling Promise.all calling the function I solved the issue:
load(file, callback) {
fs.readFile(file, (err, fd) => {
var data = JSON.parse(fd);
for(var i of data.array){
this.list.push(new Thing(i.id));
}
Promise.all(this.list.map(i => i.load()).then(callback);
});
}
You'd have to have some state inside of Thing to track it's doneness for example you could have an instance variable that's a promise. So given this hacked together example of Thing
class Thing {
constructor(id) {
this.id = id;
this.done = new Promise((resolve, reject) => {
asyncThingWithCallback((err, data) {
if (err) {
this.asyncVal = null;
reject(err);
} else {
this.asyncVal = data;
resolve()
}
})
});
}
}
You can use the done property inside of your callback like this:
load(file, callback) {
fs.readFile(file, (err, fd) => {
var data = JSON.parse(fd);
for(var i of data.array){
this.list.push(new Thing(i.id)); // new Thing is Asynchronous
}
Promise.all(this.list.map((thing) => thing.done))
.then(callback)
});
}
First off, it's generally not advisable to have a constructor that needs some asynchronous operation to finish creating a valid object. That just doesn't lead to easily writable or maintainable code because the constructor has to return the object reference and because the operation is asynchronous, it has to return that object reference before you're done creating a valid object. That just leads to messy, partially created objects. You can make it work by requiring a completion callback be passed to the constructor and making sure the calling code does not attempt to use the object until after the completion callback has been called, but this is just not a clean way to do things. It also makes it impossible to have your async operation return a promise (which is the future of async design) because the constructor has to return the object reference so it can't return a promise.
You could embed the promise in the object, but that's messy too because the promise is only really useful during the initial async operation.
What is often done instead is to make the constructor be only synchronous and then have a .init() method that does the async parts. That makes for cleaner code and is compatible with implementations using promises.
Or, you can create a factory function that returns a promise that resolves to the object reference.
Second off, as you already seem to know, your for loop runs synchronously. It doesn't "wait" for any async operations inside it to complete before going onto the next part of the loop. As long as each invocation of the loop is separate and doesn't depend upon the prior iteration, that's all fine. All you need to know is when all the async operations in the loop are done and making your async operations return promises and using Promise.all() is generally the best tool for that.
So, let's supposed you use the .init() method scheme where .init() does the async part of the initialization and the constructor is synchronous and .init() returns a promise. Then, you could do this:
// create all the things objects
let things = data.array.map(i => new Thing(i.id));
// initialize them all asynchronously
Promise.all(things.map(item => {
return item.init();
})).then(function() {
// all things are asynchronously initialized here
});
Or, using the concept of a factory function that returns a promise that resolves to the object:
function newThing(i) {
let o = new Thing(i.id);
return o.init().then(function() {
// resolve to the object itself
return o;
});
}
Promise.all(data.array.map(i => newThing(i))).then(things => {
// all things in the array ready to be used here
});
If you need to sequence your array iteration so the 2nd iteration did not start until the async part of the first iteration was done and 3rd waited until the 2nd iteration was done and so on, then you can't use a for loop because it simply doesn't work that way. There are several different ways to do such a serialized async iteration. You can see several different schemes in these other posts:
How to synchronize a sequence of promises?
JavaScript: Perform a chain of promises synchronously
ES6 Promises - something like async.each?
How can I execute shell commands in sequence?
You can use primise.all to run all the promises after the for loop .Then you can resolve the promise.all .
load(file) {
fs.readFile(file).Then(function (fd){
var data = JSON.parse(fd);
var EachPromise = [ ];
for(var i of data.array){
EachPromise.push(new Thing(i.id)); // new Thing is Asynchronous
}
Promise.all(EachPromise) .then(function (result){
console.log('this is result',result);
}).Catch(function (error){
console.log('this is error', error);
});
}

Categories