Chain execution of array of promises in javascript - javascript

I am trying to create a chain of promises where each promise waits for the previous promise before getting executed.
const syncStatusChanges = () => {
return new Promise((resolve, reject) => {
console.log("in")
setTimeout(() => {
console.log("done")
resolve({ done: true })
}, 2000);
});
}
const run = () => {
const promises = [syncStatusChanges(), syncStatusChanges()]
promises[0].then(res => {
console.log("done 1")
promises[1].then(res => {
console.log("done 2")
})
})
}
run()
In this example the output is:
in
in
done
done 1
done
done 2
But I want it to be:
in
done
done 1
in
done
done 2
I also want it to work for any n number of functions. I saw this answer but the output is the same!
var promise = statusChangeCalls[0];
for (var i = 1; i < statusChangeCalls.length; i++)
promise = promise.then(statusChangeCalls[i]);

As it is written in comments. You are executing the functions in the array itself. What i understood by seeing your output. below run function can help you.
const run = () => {
const promise = syncStatusChanges();
promise.then(res => {
console.log("done 1")
syncStatusChanges().then(res => {
console.log("done 2")
})
})
}
Promise executes eagerly. It does not wait to register the then function. You can look for observable, they are lazy in execution. Basically they wait until you subscribe them.
For your second doubt about loop. You can use async await keyword to achieve chaining. Just pass number as an parameter in runInLoop function to execute promise that many times.
const runInLoop = async(numberOfPromisesCall)=> {
for (let i = 0; i < numberOfPromisesCall; i++){
await syncStatusChanges();
console.log(`done ${i + 1}`);
}
}
runInLoop(5)

Related

How to make JS wait for previous lines of code to finish first?

So i have an issue with JS and nodeJS, and is that it runs whole code at the same time and doesnt wait for a previous function to finish its work (compared to python). How do i make it first finish its function, push the results to array and only then print to console the whole array? await doesnt seem to work in any kind of for loop
const fetch = require('node-fetch')
const fetchlink = async (i) => {
let url = `http://linktofetch`
let response = await fetch(url, {
method: 'GET'
})
const answer = await response.json()
return answer
}
const arr = []
let pushtoarr = async (value) => {
arr.push(value)
}
let main = async () => {
for(let i=1;i < 10; i++){
const answer = fetchlink(i).then((response) => {
response.data.items.forEach(el =>{
pushtoarr(el.name)
}
)
})
}
console.log(arr)
}
main()
When doing foo.then(bar), bardoesn't execute immediately, instead you're just registering a callback that will execute bar later on, and you should instead be doing const baz = await foo; bar(baz).
So, in your example, you should rewrite your code as:
const fetch = require('node-fetch')
const fetchlink = async (i) => {
let url = `http://linktofetch`;
let response = await fetch(url, { method: 'GET' });
const answer = await response.json();
return answer;
}
(async () => {
const arr = [];
for (let i=1; i<10; i++) {
const response = await fetchLink(i);
for (const el of response.data.items) {
arr.push(el.name);
}
}
console.log(arr);
})();
Didn't test but it will look somehow like this
const fetch = require('node-fetch')
(async () => {
const arr = []
for(let i=1;i < 10; i++) {
const response = await fetchlink(i)
const answer = response.data.items.forEach(el => arr.push(el))
}
console.log(arr)
})()
async function fetchlink (i) {
let url = `http://linktofetch`
let response = await fetch(url, {
method: 'GET'
})
return response.json()
}
The problem is that you're trying to do an asynchronous task synchronously. There are generally two ways you can go about executing an async function and which one you use depends on what you need from the function.
Non-Blocking
In general, an async function will return a Promise. In order to get the results of a promise you have to unwrap it like so,
asyncFunction(args).then((promiseResult) => { doStuff(promiseResult); });
The key part is that you unwrap the promise using then which will only trigger after the original promise has finished. This means that code execution will not wait for the promise to get unwrapped to execute the lines after. For example:
asyncFunction(args).then((promiseResult) => { doStuff(promiseResult); });
console.log('done');
In this case the log function will generally happen before the doStuff function gets called.
Blocking
In the event that you want to block or wait for a promise to unwrap, you need to use the await keyword like so,
const promiseResult = await asyncFunction(args);
doStuff(promiseResult);
console.log('done');
In this example, no code after the await line will get executed until the asyncFunction resolves. The important thing to understand is that it only is true within the scope of code you are in. If there is a non-blocking async function being executed inside of asyncFunction, it will not wait to finish resolving that before returning to doStuff.
I will omit the actual modification to fix your code as it seems a few other people have beat me to that, however, I hope that explanation helps.
Use promise
example usage below
'use strict';
var promiseCount = 0;
function testPromise() {
let thisPromiseCount = ++promiseCount;
let log = document.getElementById('log');
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') Started (<small>Sync code started</small>)<br/>');
// We make a new promise: we promise a numeric count of this promise, starting from 1 (after waiting 3s)
let p1 = new Promise(
// The executor function is called with the ability to resolve or
// reject the promise
(resolve, reject) => {
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') Promise started (<small>Async code started</small>)<br/>');
// This is only an example to create asynchronism
window.setTimeout(
function() {
// We fulfill the promise !
resolve(thisPromiseCount);
}, Math.random() * 2000 + 1000);
}
);
// We define what to do when the promise is resolved with the then() call,
// and what to do when the promise is rejected with the catch() call
p1.then(
// Log the fulfillment value
function(val) {
log.insertAdjacentHTML('beforeend', val +
') Promise fulfilled (<small>Async code terminated</small>)<br/>');
}).catch(
// Log the rejection reason
(reason) => {
console.log('Handle rejected promise ('+reason+') here.');
});
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') Promise made (<small>Sync code terminated</small>)<br/>');
}
reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

How to wait for 2 of Promises in Promise.all to finish and then do another task

I'm currently developing a program where I need to use Promise.all to run a few functions at the same time. But before I can continue with the task, I need ONLY 2 of the promises to finish and then run the .then(), how would I go about doing this?
Example:
await Promise.all([Task1(),Task2(),Task3(),Task4(),Task5()]);
I need it to continue the code when only (for example) Task1 and Task 4 have finished.
I have tried to experiment by using a while loop waiting for Task1 and Task2 finish by settings variables on the finish, but that. doesn't work at all.
In a comment you seem to have said you specifically know that in advance which two are more urgent than the rest (and that in this example, it's Task1 and Task4).
Then just use Promise.all twice:
const allResults = Promise.all([
Promise.all([Task1(), Task4()])
.then(([result1, result4]) => {
// Those two are done, do what you like with `result1` and `result4`...
return [result1, result4];
}),
Task2(),
Task3(),
Task5()
])
.then(([[result1, result4], result2, result3, result5]) => {
// All five are done now, let's put them in order
return [result1, result2, result3, result4, result5];
})
.then(/*...*/)
.catch(/*...*/);
In there, I've preserved the overall 1, 2, 3, 4, 5 order in the outer chain by remapping the order in the overall then handler.
Originally, I assumed you wanted to wait until any two have finished, rather than a specific two. There's no built-in for that, but it's easy enough to write:
function enough(promises, min) {
if (typeof min !== "number") {
return Promise.all(promises);
}
let counter = 0;
const results = [];
return new Promise((resolve, reject) => {
let index = 0;
for (const promise of promises) {
let position = index++;
promise.then(
result => {
results[position] = result;
if (++counter >= min) {
resolve(results);
}
},
reject
);
}
});
}
Live Example:
function enough(promises, min) {
if (typeof min !== "number") {
return Promise.all(promises);
}
let counter = 0;
const results = [];
return new Promise((resolve, reject) => {
let index = 0;
for (const promise of promises) {
let position = index++;
promise.then(
result => {
results[position] = result;
if (++counter >= min) {
resolve(results);
}
},
reject
);
}
});
}
const delay = (ms, ...args) => new Promise(resolve => setTimeout(resolve, ms, ...args));
const rnd = () => Math.random() * 1000;
enough(
[
delay(rnd(), "a"),
delay(rnd(), "b"),
delay(rnd(), "c"),
delay(rnd(), "d"),
delay(rnd(), "e")
],
2
)
.then(results => {
console.log(results);
})
.catch(error => {
console.error(error);
});
One way to do it is by constructing a new array of the randomized promises, and then wait for those only:
let array = [Task1(),Task2(),Task3(),Task4(),Task5()];
// Select any two promises after running the randomization logic
let promises = Promise.all[array[1], array[3]];
promises
.then(() => {
// Do stuff here
});
I've seen tricks like this :
Promise.all(promises.map(p => p.catch(() => undefined)));
Quite unsafe though.
Original answer : here
OK, as I understand you, you want to do something like
const importantOnes = [Task1(), Task2()];
const remainingOnes = [Task3(), Task4(), Task5()];
const priorityPromise = Promise.all(importantOnes);
priorityPromise.then(doPriorityStuff);
Promise.all([priorityPromise, ...remainingOnes]).then(processTheCompleteData);
Promise.all() does not run your tasks simultaneously, it only awaits all Promises to resolve before resolving the returned Promise.
Your tasks will be run as soon as you create each Promise.
If you want to wait after specific tasks, only include those tasks in Promise.all:
const tasks = [Task2(), Task3(), Task5()];
const result1 = await Promise.all([Task1(), Task4()]);
// Task1 and Task4 are done here
const result2 = await Promise.all(tasks);
// All remaining tasks are done here

make an api call in foreach loop and push the result in another array

I am trying to make an api call in a for each loop. When I make an api call, the function returns the Promise. I want use that return response and check if the promise is resolved (then do something) and if rejected (then do something). But, everytime in the foreach loop, first the api makes both the calls and then goes to the then() part.
What I want is in foreach loop, make an api call -> check if resolved or rejected -> do something -> make an api call -> check if resolved or rejected -> do something and so on.
//cannot change the below implementation. I need to use this already created function
const callApiFunction = options => {
const callback = (resolve, reject) => {
something.call(options, resolve, reject)
};
return new Promise(callback);
}
//First attempt
const a =[];
const b =[];
list.forEach(listelement => {
callApiFunction(options)
.then(() => {
a.push(listelement);
})
.catch(() => {
b.push(listelement);
});
});
//Second attempt
const a =[];
const b =[];
list.forEach(listelement => {
let promise = new Promise((resolve, reject) => {
callApiFunction(options);
resolve(listelement);
reject(listelement);
});
promise.then((listelement) => {
a.push(listelement);
})
.catch((listelement) => {
b.push(listelement);
});
});
What happens in my second code block is, callApiFunction is first executed twice if list.length = 2 and then, then is executed twice.
I want the callApiFunction is executed and list[0] -> then or catch executes accordingly
callApiFunction is executed and list[1] -> then or catch executes accordingly
Does this variant with for-of loop and await work for you?
'use strict';
async function separate(list) {
const a = [];
const b = [];
for (const listelement of list) {
try {
await callApiFunction(options);
a.push(listelement);
} catch (err) {
b.push(listelement);
}
}
return [a, b];
}
separate([1, 2, 3]).then(([good, bad]) => {
console.log(`Success:\n${good.join('\n')}\n\nErrors:\n${bad.join('\n')}\n`);
});

Synchronize multiple Promises while allowing multiple number of retries

I am trying to build a downloader that automatically retries downloading. Basically, a task queue which retries tasks for a certain number of times. I first tried using Promise.all() but the "trick" to circumvent the fail-on-first-reject described here did not help (and is an anti-pattern as described further down in that thread)
So I got a version working which seems to somewhat do what I want. At least the results it prints are correct. But it still throws several uncaught exception test X errors/warnings and I don't know what to do about that.
The Code:
asd = async () => {
// Function simulating tasks which might fail.
function wait(ms, data) {
return new Promise( (resolve, reject) => setTimeout(() => {
if (Math.random() > 0.5){
resolve(data);
} else {
reject(data);
}
}, ms) );
}
let tasks = [];
const results = [];
// start the tasks
for ( let i = 0; i < 20; i++) {
const prom = wait(100 * i, 'test ' + i);
tasks.push([i, prom]);
}
// collect results and handle retries.
for ( let tries = 0; tries < 10; tries++){
failedTasks = [];
for ( let i = 0; i < tasks.length; i++) {
const task_idx = tasks[i][0];
// Wait for the task and check whether they failed or not.
// Any pointers on how to improve the readability of the next 6 lines appreciated.
await tasks[i][1].then(result => {
results.push([task_idx, result])
}).catch(err => {
const prom = wait(100 * task_idx, 'test ' + task_idx);
failedTasks.push([task_idx, prom])
});
}
// Retry the tasks which failed.
if (failedTasks.length === 0){
break;
} else {
tasks = failedTasks;
}
console.log('try ', tries);
}
console.log(results);
}
In the end, the results array contains (unless a task failed 10 times) all the results. But still uncaught exceptions fly around.
As not all rejected promises result in uncaught exceptions, my suspicion is, that starting the tasks first and applying then()/catch() later is causing some timing issues here.
Any improvements or better solutions to my problems are appreciated. E.g. my solution only allows retries "in waves". If anyone comes up with a better continuous solution, that would be much appreciated as well.
Using await and asnyc allows to solve that in a much clearer way.
You pass an array of tasks (functions that when executed start the given task) to the execute_tasks. This function will call for each of those tasks the execute_task, passing the task function to it, the execute_task will return a Promise containing the information if the task was successful or not.
The execute_task as a loop that loops until the async task was successful or the maximum number of retries reached.
Because each of the tasks has its own retry loop you can avoid those waves. Each task will queue itself for a new execution as it fails. Using await this way creates some kind of cooperative multitasking. And all errors are handled because the task is executed in a try catch block.
function wait(ms, data) {
return new Promise((resolve, reject) => setTimeout(() => {
if (Math.random() > 0.5) {
resolve(data);
} else {
reject(new Error());
}
}, ms));
}
async function execute_task(task) {
let result, lastError;
let i = 0
//loop until result was found or the retry count is larger then 10
while (!result && i < 10) {
try {
result = await task()
} catch (err) {
lastError = err
// maybe sleep/wait before retry
}
i++
}
if (result) {
return { success: true, data: result }
} else {
return { success: false, err: lastError }
}
}
async function execute_tasks(taskList) {
var taskPromises = taskList.map(task => execute_task(task))
// the result could be sorted into failed and not failed task before returning
return await Promise.all(taskPromises)
}
var taskList = []
for (let i = 0; i < 10; i++) {
taskList.push(() => {
return wait(500, {
foo: i
})
})
}
execute_tasks(taskList)
.then(result => {
console.dir(result)
})

JavaScript: Asynchronous method in while loop

I'm tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.
This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:
var done = true;
do
{
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// Sets the boolean to true if the returned array is empty, or false otherwise.
done = (result.data().length === 0) ? true : false;
}
}
);
} while (!done);
This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.
Help would be appreciated!
edit: see the bottom, there is the real answer.
I encourage you yo use the Promise API. Your problem can be solved using a Promise.all call:
let promises = [];
while(something){
promises.push(new Promise((r, j) => {
YourAsyncCall(() => r());
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => {
//All operations done
});
The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):
var promises = [];
while(something){
promises.push(new Promise(function(r, j){
YourAsyncCall(function(){ r(); });
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function(){
//All operations done
});
You can also make your api call return the promise and push it directly to the promise array.
If you don't want to edit the api_call_method you can always wrap your code in a new promise and call the method resolve when it finishes.
edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.
You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.
Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.
So the good answer
You can use a promise to control the flow of your application and use recursion instead of the while loop:
function asyncOp(resolve, reject) {
//If you're using NodeJS you can use Es6 syntax:
async_api_call("method.name", {}, (result) => {
if(result.error()) {
console.error(result.error());
reject(result.error()); //You can reject the promise, this is optional.
} else {
//If your operation succeeds, resolve the promise and don't call again.
if (result.data().length === 0) {
asyncOp(resolve); //Try again
} else {
resolve(result); //Resolve the promise, pass the result.
}
}
});
}
new Promise((r, j) => {
asyncOp(r, j);
}).then((result) => {
//This will call if your algorithm succeeds!
});
/*
* Please note that "(...) => {}" equivals to "function(...){}"
*/
sigmasoldier's solution is correct, just wanted to share the ES6 version with async / await:
const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));
const getData = async (resolve, reject, count) => {
console.log('waiting');
await asyncFunction(3000);
console.log('finshed waiting');
count++;
if (count < 2) {
getData(resolve, reject, count);
} else {
return resolve();
}
}
const runScript = async () => {
await new Promise((r, j) => getData(r, j, 0));
console.log('finished');
};
runScript();
If you don't want to use recursion you can change your while loop into a for of loop and use a generator function for maintaining done state. Here's a simple example where the for of loop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.
let done = false;
let count = 0;
const whileGenerator = function* () {
while (!done) {
yield count;
}
};
const asyncFunction = async function(){
await new Promise(resolve => { setTimeout(resolve); });
};
const main = new Promise(async (resolve)=>{
for (let i of whileGenerator()){
console.log(i);
await asyncFunction();
count++;
if (count === 5){
done = true;
}
}
resolve();
});
main.then(()=>{
console.log('all done!');
});
Also you may try recursion solution.
function asyncCall(cb) {
// Some async operation
}
function responseHandler(result) {
if (result.error()) {
console.error(result.error());
} else if(result.data() && result.data().length) {
asyncCall(responseHandler);
}
}
asyncCall(responseHandler);
Here is a solution I came up with. Place this in an async function.
let finished = false;
const loop = async () => {
return new Promise(async (resolve, reject) => {
const inner = async () => {
if (!finished) {
//insert loop code here
if (xxx is done) { //insert this in your loop code after task is complete
finshed = true;
resolve();
} else {
return inner();
}
}
}
await inner();
})
}
await loop();
If you don't want to use Promises you can restructure your code like so:
var tasks = [];
var index = 0;
function processNextTask()
{
if(++index == tasks.length)
{
// no more tasks
return;
}
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// process data
setTimeout(processNextTask);
}
}
);
}
Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:
//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject){
return new Promise((resolve, reject) => {
async_api_call(methodName, someObject, function(result){
if(result.error()){
reject( result.error() )
}else{
resolve( result.data() )
}
});
})
}
now to your polling code:
//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", {/*Do stuff.*/});
//your pulling operation
poll().then(
data => data.length === 0 || poll(), //true || tryAgain
err => {
console.error(err);
return poll();
}
).then((done) => {
//done === true
//here you put the code that has to wait for your "loop" to finish
});
Why Promises? Because they do state-management of async operations. Why implement that yourself?
let taskPool = new Promise(function(resolve, reject) {
resolve("Success!");
});
let that = this;
while (index < this.totalPieces) {
end = start + thisPartSize;
if (end > filesize) {
end = filesize;
thisPartSize = filesize - start;
}
taskPool.then(() => {
that.worker(start, end, index, thisPartSize);
});
index++;
start = end;
}

Categories

Resources