Call external API until request is marked as complete - node.js

I have following situation.
I have a service which runs jobs on a remote service and exposes API for calling the same.
I have an array of jobs which needs to be executed on remote server and it works fine, when used Promises.
The flow is as below
Inside main function I get a token. On .then() of the same, I initiate my for loop for jobs and pass the JobID and token. This second function returns me the execution_ID of each job. On the .then() of this second function I pass token and execution_id. This returns me the status of the job.
My problem is; when a job is executed, it send me queued, initiated and completed as status. When the status turns to 'completed' I get results of the job; which I need to display.
I tried using a setTimeOut() inside the last function, but I'm not sure when it will end as each job may take different time.
Is there a way I can call this third function multiple time unless the status changes to 'completed'?
Below is the code
app.get('/ExecuteJobs', function (req, res) {
var commandArraay = ["job1", "job2"]
var sTokenID;
getAuthToken()
.then(function (token) {
commandArraay.forEach(function (element) {
getExecutionID(token, element)
.then(function (executionResult) {
setTimeout(() => {
getExecutionResult(token, executionResult.id, executionResult)
.then(function (updateArray) {
console.log("Final Status " + "ID: " + executionResult.id + " Status: " + executionResult.status);
// if(executionResult.)
})
}, 10000)
})
})
})
res.send('Done');
});
// Function to get the auth token
async function getAuthToken(token) {
return new Promise(function (resolve, reject) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
var pUserID = 'uname'
var pPwd = 'pwd'
performhttpsRequest('/<remote_api>/tokens', 'POST', {
sAuth: "Basic " + new Buffer(pUserID + ":" + pPwd).toString("base64")
}, "0", token,
function (data1) {
sTokenID = data1.token;
resolve(sTokenID);
})
})
}
// Function to post the command and get execution ID
async function getExecutionID(tokenID, command, executionID) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/executecommand', 'POST', {
command: command
}, "1", tokenID,
function (data1) {
var executionID = data1.execution_id;
resolve(executionID);
})
})
}
// Function to get the execution results for an ID
async function getExecutionResult(tokenID, executionID, result) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/execution_result/' + executionID, 'GET', {
}, "1", tokenID,
function (result) {
resolve(result.result);
})
})
}

If you absolutely need a result after the enqueued job is settled, then I don't see how you can have any other choice other than retrying to check the status every n-seconds.
Here's how I would do it. A recursive function that retries a request n-times, each time waiting n-seconds:
// Mock request, resolves { status: 'completed' } 20% of the time.
const request = () => {
return new Promise(resolve => {
Math.random() < 0.2
? resolve({ status: 'completed', foo: 'bar' })
: resolve({ status: 'pending' })
})
}
const retryToCompletion = async ({ wait, retries }) => {
console.log('Retries left:', retries)
const result = await new Promise((resolve, reject) => {
setTimeout(() => request().then(resolve).catch(reject), wait)
})
if (result.status === 'completed')
return result
if (retries)
return retryToCompletion({ wait, retries: --retries })
throw new Error('Retry attempts exhausted')
}
retryToCompletion({ wait: 1000, retries: 5 })
.then(console.log)
.catch(console.error)
That being said, some API's that work with BASE queues offer a handle to a WebSocket connection that notifies when a job is completed. If the API offers that, then you should ditch retrying and use the completed notification instead.

making GET calls until completion
/**
* cbk be called when..complete execution ok
*/
function completeExecution(tokenID, executionID, result, callPeriod, cbk){
return getExecutionResult(tokenID, executionID, result).then(res=>{
if(res.completed){
return cbk(null, res)
}
setTimeout(function(){
return completeExecution(cbk); //reenqueue
}, callPeriod)
}).catch(cbk) //up to you to abort or insist
}
Then you can promisify completeExecution (with util.promisify or by yourself)

Related

How can I am make sure these chain of functions in Node.js are performed in order (using promises)?

I have a set of functions in Node.js that I would like to load in a certain order. I will provide some mockup code abstracted and simplified:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData is a bit more complex. In it I would like to, in order:
console.log('Starting alterData()')
Load data, as json, from the cloud data source
Iterate through every item in the json file and do handleDataItem(item) on it.
When #2 is done -> console.log('alterData() done')
Return a resolved promise back to updateMyApp
Go on with saveData() with all data altered.
I want the logs to show:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
my take on this is the following:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
which seems to work but, as far as I understand this is not how one is supposed to do it. Also, this seems to do the handleDataItem outside of this chain so the logs look like this:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
In other words. It doesn't seem like the handleDataItem() calls are finished when the chain has moved on to the next step (.then()). In other words, I can not be sure all items have been updated when it goes on to the saveData() function?
If this is not a good way to handle it, then how should these functions be written? How do I chain the functions properly to make sure everything is done in the right order (as well as making the log events appear in order)?
Edit: As per request, this is handleDataItem less abstracted.
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
The twitterAPI functions called:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('#', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
You have 3 options to deal with your main issue of async methods in a loop.
Instead of forEach, use map and return promises. Then use Promise.all on the returned promises to wait for them to all complete.
Use a for/of loop in combination with async/await.
Use a for await loop.
It sounds like there's a problem in the implementation of handleDataItem() and the promise that it returns. To help you with that, we need to see the code for that function.
You also need to clean up useData() so that it properly returns a promise that propagates both completion and errors.
And, if handleDataItem() returns a promise that is accurate, then you need to change how you do that in a loop here also.
Change from this:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
to this:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
The structural changes here are:
Switch to use async/await to more easily handle the asynchronous items in a loop
Remove the promise anti-pattern that wraps new Promise() around an existing promise - no need for that AND you weren't capturing or propagating rejections from readFromCloudFileServer() which is a common mistake when using that anti-pattern.
rethrow the error inside your catch after logging the error so the error gets propagated back to the caller

Asynchronously generate list of reverse geocoded addresses without API rate limiting

I'm generating test/development dummy data with a node script in my Mongo database (using Mongoose) which includes Geolocation coordinates. (sets of lat/lon). Schema follows:
location: {
type: {
type: String,
enum: ["Point"], // 'location.type' must be 'Point'
default: "Point",
},
coordinates: {
type: [Number],
required: true,
},
geocoded: {
type: String, // this has to be done with an external API
},
},
For that reason, I have an external (paid) Reverse Geocoding API which I want/need to call for each document/set of coordinates. The Geocoding API though has a rate limiter so I'm hitting 429 - too many requests. I'm looking for a clean and simple solution to run my requests sequentially and add a throttling/waiting time ( for a specified number of milliseconds ) after each HTTP request.
messageSchema.pre("insertMany", async function save(next, docs) {
docs.map(async (doc) => { // now I understand I should replace map with for ... of or for ... in
[err, response] = await to(
reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
)
);
if (err) {
next(err);
}
doc.location.geocoded = response;
});
});
The reverseGeocode signature:
reverseGeocode: (lon, lat) =>
axios({
baseURL: "https://eu1.locationiq.com/",
url: "v1/reverse.php",
params: {
lat,
lon,
key: geocodeKey,
},
}).then((response) => response.data),
I use this library for throttling requests. You simply tell it what the rate limit of the API is, and you can call it as fast as you want and it will automatically space the requests out over time for you.
If you don't want another dependency, then to make your solution work you need to use a for loop. map will always execute as fast as it possibly can.
const wait = (time) => {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
messageSchema.pre("insertMany", async function(next, docs) {
for(let i in docs) {
const doc = docs[i];
await wait(3000); // milliseconds to space requests out by.
const response = await reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
);
}
console.log(this);
});
There's no need to "fork" the process as nodejs has native async promise support.
The best solution is actually to add delays to your code to match the limit. Assuming this is too cumbersome and the fact that this is only for development purposes here is a quick brute force example:
Note that waiting on a promise does not block the process as synchronous code would.
async function getGeoCode(doc) {
return reverseGeocode(
doc.location.coordinates[0],
doc.location.coordinates[1]
)
}
const randomSleep = [1000, 2000, 3000, 4000, 5000, 6000];
messageSchema.pre("insertMany", async function save(next, docs) {
let sleep = false;
docs.map(async (doc) => {
while (sleep) {
const randomSleep = randomSleep[Math.floor(Math.random() * randomSleep.length)];
await new Promise(function (resolve) {
setTimeout(resolve, 2000 + randomSleep)
});
}
let [err, response] = await getGeoCode(doc);
if (err) {
if (err.statusCode !== 429) {
throw new Error(err);
}
sleep = true;
while (err && err.statusCode === 429) {
const randomSleep = randomSleep[Math.floor(Math.random() * randomSleep.length)];
await new Promise(function (resolve) {
setTimeout(resolve, 2000 + randomSleep)
});
[err, response] = await getGeoCode(doc);
}
sleep = false;
}
});
console.log(this);
});

Prevent request to wait more than 10s if api doesn't respond

I saw that if a request has no response, it waits more then 60s.
In my case I have nested async loop, managed with callbacks and promise. Each element is a api call.
So I want to detect for example if an api don't respond after 10seconds to go ahead.
My code for the api requests :
return new Promise((resolve, reject) => {
var start = moment();
const req = adapterFor(url).request(options, (resp) => {
//console.log(`El pr ${options.path}`);
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
try {
tmpData = JSON.parse(data.trim());
if(tmpData.length != 0 && tmpData.films.length > 0) {
data = tmpData.films.filter(v => {
return v.film_o.length > 0
})
resolve(data);
}else resolve({"Errore" : url,error:some error',error_type:'some error',duration: ((moment() - start) / 1000)+' s'});
}catch (e) {
resolve({"Errore" : url,'error':'HERE MAYBE',error_type:'?!',duration: ((moment() - start) / 1000)+' s'});
}
// console.log(colors.gray("EL: " + tmpData.DS.Scheduling.Events.length.length));
});
});
req.end();
})
If you can utilize ESnext then you can use Promise.race(). Consider the following example:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 5000)),
new Promise((resolve, reject) => setTimeout(() => resolve(2), 10000))
]).then(res => console.log(res))
You can provide multiple promises in an Array and Promise.race will always choose the fastest and resolve this one. So you can take your promise as one element and a timeout as shown above as the second. This promise will then always resolve after the timeout, or when your api call is done. Whatever is first will be resolved.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
https://jsfiddle.net/yLv64zr5/
One option is to indicate a flag to reject after the time configured. In the example below you can see that the variable reqHasFinished is changed inside the request callback, so in case its keeps false you can reject the request:
return new Promise((resolve, reject) => {
let start = moment(),
reqHasFinished = false;
const req = adapterFor(url).request(options, (resp) => {
reqHasFinished = true;
let data = '';
// resp operations here
});
// if after 10 seconds the request has not finished, the promise is rejected.
setTimeout(() => {
if (!reqHasFinished) {
reject('Go ahead');
}
}, 10000)
req.end();
});

Promise returning deferred.promise test using Mocha

Bot Info
SDK Platform: Node.js
Issue Description
Code Example
resolveMeetingRoomFreeTime: function (session, args) {
return new Promise(function (resolve, reject) {
kgService.getFreeMeetingRooms(session.conversationData.floorEntityList,
session.conversationData.roomEntityList,
session.conversationData.meetingStartTimestamp,
session.conversationData.meetingEndTimestamp,
session.conversationData.meetingStartTime,
session.conversationData.meetingEndTime,
session.conversationData.meetingDuration)
.then(function (roomReservationList) {
**let returnResults = roomReservationList;
session.conversationData.meetingRoomAvailabilityList = roomReservationList;
setTimeout(function () { resolve(returnResults); }, 1000);**
})
.catch(function (error) {
console.log("There is error from getFreeMeetingRooms() "+error);
});
}
)
}
Method getFreeMeetingRooms() looks like as follows.
getFreeMeetingRooms: function (nearestFloorEntity, roomEntityList, startEpoch, endEpoch, startHour, endHour, meetingDuration) {
let deferred = Q.defer();
let query = function (resolve, reject) {
graphDB.cypher({
query: cypherQuery,
params: {
nearestFloorEntity, roomEntityList, startEpoch, endEpoch, startHour, endHour, meetingDuration
}
}, function (err, results) {
return deferred.resolve(results);
});
query();
return deferred.promise;
}
My test case using Mocha and Sion Stub:
it('Should return default value for resolveMeetingRoomFreeTime()', function () {
let args = {};
let session = {
conversationData: {
roomEntityList: [{ id: "ABC", name: "ABC", capacity: 8}],
floorEntityList: "9",
meetingStartTimestamp: 1521659679948,
meetingEndTimestamp: 1521781200000,
meetingStartTime: 1514815200000,
meetingEndTime: 1514847600000,
meetingDuration: 30
}
};
//stubbing call for getFreeMeetingRooms()
var stub = sinon.stub(calendarNeo4jService, "getFreeMeetingRooms")
.withArgs(session.conversationData.floorEntityList,
session.conversationData.roomEntityList,
session.conversationData.meetingStartTimestamp,
session.conversationData.meetingEndTimestamp,
session.conversationData.meetingStartTime,
session.conversationData.meetingEndTime,
session.conversationData.meetingDuration);
var error= calendarEntityResolver.resolveMeetingRoomFreeTime(session, args)
error.then(function name(params) {
})
.then(function (error) {
});
});
Questions:
Not sure if using stub is correct if I need to skil DB call graphDB.cypher from method getFreeMeetingRooms().
How do I test block of code from resolveMeetingRoomFreeTime() which is highlighted.
If stubbing kgService.getFreeMeetingRooms is correct the how would test catch block.
I think you're asking how to test an async function using mocha. If that's true, you only need to return your promise inside the it function. Mocha will wait for the promise to resolve or reject before marking it successful, which will also allow assertions in the then function.

(node.js version 7 or above, not C#) multiple await call with node.js [duplicate]

As far as I understand, in ES7/ES2016 putting multiple await's in code will work similar to chaining .then() with promises, meaning that they will execute one after the other rather than in parallel. So, for example, we have this code:
await someCall();
await anotherCall();
Do I understand it correctly that anotherCall() will be called only when someCall() is completed? What is the most elegant way of calling them in parallel?
I want to use it in Node, so maybe there's a solution with async library?
EDIT: I'm not satisfied with the solution provided in this question: Slowdown due to non-parallel awaiting of promises in async generators, because it uses generators and I'm asking about a more general use case.
You can await on Promise.all():
await Promise.all([someCall(), anotherCall()]);
To store the results:
let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
Note that Promise.all fails fast, which means that as soon as one of the promises supplied to it rejects, then the entire thing rejects.
const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))
Promise.all([happy('happy', 100), sad('sad', 50)])
.then(console.log).catch(console.log) // 'sad'
If, instead, you want to wait for all the promises to either fulfill or reject, then you can use Promise.allSettled. Note that Internet Explorer does not natively support this method.
const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))
Promise.allSettled([happy('happy', 100), sad('sad', 50)])
.then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]
Note: If you use Promise.all actions that managed to finish before rejection happen are not rolled back, so you may need to take care of such situation. For example
if you have 5 actions, 4 quick, 1 slow and slow rejects. Those 4
actions may be already executed so you may need to roll back. In such situation consider using Promise.allSettled while it will provide exact detail which action failed and which not.
TL;DR
Use Promise.all for the parallel function calls, the answer behaviors not correctly when the error occurs.
First, execute all the asynchronous calls at once and obtain all the Promise objects. Second, use await on the Promise objects. This way, while you wait for the first Promise to resolve the other asynchronous calls are still progressing. Overall, you will only wait for as long as the slowest asynchronous call. For example:
// Begin first call and store promise without waiting
const someResult = someCall();
// Begin second call and store promise without waiting
const anotherResult = anotherCall();
// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];
// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise
JSbin example: http://jsbin.com/xerifanima/edit?js,console
Caveat: It doesn't matter if the await calls are on the same line or on different lines, so long as the first await call happens after all of the asynchronous calls. See JohnnyHK's comment.
Update: this answer has a different timing in error handling according to the #bergi's answer, it does NOT throw out the error as the error occurs but after all the promises are executed.
I compare the result with #jonny's tip: [result1, result2] = Promise.all([async1(), async2()]), check the following code snippet
const correctAsync500ms = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, 'correct500msResult');
});
};
const correctAsync100ms = () => {
return new Promise(resolve => {
setTimeout(resolve, 100, 'correct100msResult');
});
};
const rejectAsync100ms = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, 'reject100msError');
});
};
const asyncInArray = async (fun1, fun2) => {
const label = 'test async functions in array';
try {
console.time(label);
const p1 = fun1();
const p2 = fun2();
const result = [await p1, await p2];
console.timeEnd(label);
} catch (e) {
console.error('error is', e);
console.timeEnd(label);
}
};
const asyncInPromiseAll = async (fun1, fun2) => {
const label = 'test async functions with Promise.all';
try {
console.time(label);
let [value1, value2] = await Promise.all([fun1(), fun2()]);
console.timeEnd(label);
} catch (e) {
console.error('error is', e);
console.timeEnd(label);
}
};
(async () => {
console.group('async functions without error');
console.log('async functions without error: start')
await asyncInArray(correctAsync500ms, correctAsync100ms);
await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
console.groupEnd();
console.group('async functions with error');
console.log('async functions with error: start')
await asyncInArray(correctAsync500ms, rejectAsync100ms);
await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
console.groupEnd();
})();
Update:
The original answer makes it difficult (and in some cases impossible) to correctly handle promise rejections. The correct solution is to use Promise.all:
const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
Original answer:
Just make sure you call both functions before you await either one:
// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();
// Await both promises
const someResult = await somePromise;
const anotherResult = await anotherPromise;
There is another way without Promise.all() to do it in parallel:
First, we have 2 functions to print numbers:
function printNumber1() {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log("Number1 is done");
resolve(10);
},1000);
});
}
function printNumber2() {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log("Number2 is done");
resolve(20);
},500);
});
}
This is sequential:
async function oneByOne() {
const number1 = await printNumber1();
const number2 = await printNumber2();
}
//Output: Number1 is done, Number2 is done
This is parallel:
async function inParallel() {
const promise1 = printNumber1();
const promise2 = printNumber2();
const number1 = await promise1;
const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
I've created a gist testing some different ways of resolving promises, with results. It may be helpful to see the options that work.
Edit: Gist content as per Jin Lee's comment
// Simple gist to test parallel promise resolution when using async / await
function promiseWait(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, time);
});
}
async function test() {
return [
await promiseWait(1000),
await promiseWait(5000),
await promiseWait(9000),
await promiseWait(3000),
]
}
async function test2() {
return {
'aa': await promiseWait(1000),
'bb': await promiseWait(5000),
'cc': await promiseWait(9000),
'dd': await promiseWait(3000),
}
}
async function test3() {
return await {
'aa': promiseWait(1000),
'bb': promiseWait(5000),
'cc': promiseWait(9000),
'dd': promiseWait(3000),
}
}
async function test4() {
const p1 = promiseWait(1000);
const p2 = promiseWait(5000);
const p3 = promiseWait(9000);
const p4 = promiseWait(3000);
return {
'aa': await p1,
'bb': await p2,
'cc': await p3,
'dd': await p4,
};
}
async function test5() {
return await Promise.all([
await promiseWait(1000),
await promiseWait(5000),
await promiseWait(9000),
await promiseWait(3000),
]);
}
async function test6() {
return await Promise.all([
promiseWait(1000),
promiseWait(5000),
promiseWait(9000),
promiseWait(3000),
]);
}
async function test7() {
const p1 = promiseWait(1000);
const p2 = promiseWait(5000);
const p3 = promiseWait(9000);
return {
'aa': await p1,
'bb': await p2,
'cc': await p3,
'dd': await promiseWait(3000),
};
}
let start = Date.now();
test().then((res) => {
console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test2().then((res) => {
console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test3().then((res) => {
console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test4().then((res) => {
console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test5().then((res) => {
console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test6().then((res) => {
console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
});
start = Date.now();
test7().then((res) => {
console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
});
});
});
});
});
});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
bb: Promise { <pending> },
cc: Promise { <pending> },
dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/
In my case, I have several tasks I want to execute in parallel, but I need to do something different with the result of those tasks.
function wait(ms, data) {
console.log('Starting task:', data, ms);
return new Promise(resolve => setTimeout(resolve, ms, data));
}
var tasks = [
async () => {
var result = await wait(1000, 'moose');
// do something with result
console.log(result);
},
async () => {
var result = await wait(500, 'taco');
// do something with result
console.log(result);
},
async () => {
var result = await wait(5000, 'burp');
// do something with result
console.log(result);
}
]
await Promise.all(tasks.map(p => p()));
console.log('done');
And the output:
Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
(async function(){
function wait(ms, data) {
console.log('Starting task:', data, ms);
return new Promise(resolve => setTimeout(resolve, ms, data));
}
var tasks = [
async () => {
var result = await wait(1000, 'moose');
// do something with result
console.log(result);
},
async () => {
var result = await wait(500, 'taco');
// do something with result
console.log(result);
},
async () => {
var result = await wait(5000, 'burp');
// do something with result
console.log(result);
}
]
await Promise.all(tasks.map(p => p()));
console.log('done');
})();
await Promise.all([someCall(), anotherCall()]); as already mention will act as a thread fence (very common in parallel code as CUDA), hence it will allow all the promises in it to run without blocking each other, but will prevent the execution to continue until ALL are resolved.
another approach that is worth to share is the Node.js async that will also allow you to easily control the amount of concurrency that is usually desirable if the task is directly linked to the use of limited resources as API call, I/O operations, etc.
// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
console.log('Hello ' + task.name);
callback();
}, 2);
// assign a callback
q.drain = function() {
console.log('All items have been processed');
};
// add some items to the queue
q.push({name: 'foo'}, function(err) {
console.log('Finished processing foo');
});
q.push({name: 'bar'}, function (err) {
console.log('Finished processing bar');
});
// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
console.log('Finished processing item');
});
// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
console.log('Finished processing bar');
});
Credits to the Medium article autor (read more)
You can call multiple asynchronous functions without awaiting them. This will execute them in parallel. While doing so, save the returned promises in variables, and await them at some point either individually or using Promise.all() and process the results.
You can also wrap the function calls with try...catch to handle failures of individual asynchronous actions and provide fallback logic.
Here's an example:
Observe the logs, the logs printed at the beginning of execution of the individual asynchronous functions get printed immediately even though the first function takes 5 seconds to resolve.
function someLongFunc () {
return new Promise((resolve, reject)=> {
console.log('Executing function 1')
setTimeout(resolve, 5000)
})
}
function anotherLongFunc () {
return new Promise((resolve, reject)=> {
console.log('Executing function 2')
setTimeout(resolve, 5000)
})
}
async function main () {
let someLongFuncPromise, anotherLongFuncPromise
const start = Date.now()
try {
someLongFuncPromise = someLongFunc()
}
catch (ex) {
console.error('something went wrong during func 1')
}
try {
anotherLongFuncPromise = anotherLongFunc()
}
catch (ex) {
console.error('something went wrong during func 2')
}
await someLongFuncPromise
await anotherLongFuncPromise
const totalTime = Date.now() - start
console.log('Execution completed in ', totalTime)
}
main()
// A generic test function that can be configured
// with an arbitrary delay and to either resolve or reject
const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
console.log(`Done ${ delay }`);
resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
}, delay));
// Our async handler function
const handler = async () => {
// Promise 1 runs first, but resolves last
const p1 = test(10000, true);
// Promise 2 run second, and also resolves
const p2 = test(5000, true);
// Promise 3 runs last, but completes first (with a rejection)
// Note the catch to trap the error immediately
const p3 = test(1000, false).catch(e => console.log(e));
// Await all in parallel
const r = await Promise.all([p1, p2, p3]);
// Display the results
console.log(r);
};
// Run the handler
handler();
/*
Done 1000
Reject 1000
Done 5000
Done 10000
*/
Whilst setting p1, p2 and p3 is not strictly running them in parallel, they do not hold up any execution and you can trap contextual errors with a catch.
This can be accomplished with Promise.allSettled(), which is similar to Promise.all() but without the fail-fast behavior.
async function Promise1() {
throw "Failure!";
}
async function Promise2() {
return "Success!";
}
const [Promise1Result, Promise2Result] = await Promise.allSettled([Promise1(), Promise2()]);
console.log(Promise1Result); // {status: "rejected", reason: "Failure!"}
console.log(Promise2Result); // {status: "fulfilled", value: "Success!"}
Note: This is a bleeding edge feature with limited browser support, so I strongly recommend including a polyfill for this function.
I create a helper function waitAll, may be it can make it sweeter.
It only works in nodejs for now, not in browser chrome.
//const parallel = async (...items) => {
const waitAll = async (...items) => {
//this function does start execution the functions
//the execution has been started before running this code here
//instead it collects of the result of execution of the functions
const temp = [];
for (const item of items) {
//this is not
//temp.push(await item())
//it does wait for the result in series (not in parallel), but
//it doesn't affect the parallel execution of those functions
//because they haven started earlier
temp.push(await item);
}
return temp;
};
//the async functions are executed in parallel before passed
//in the waitAll function
//const finalResult = await waitAll(someResult(), anotherResult());
//const finalResult = await parallel(someResult(), anotherResult());
//or
const [result1, result2] = await waitAll(someResult(), anotherResult());
//const [result1, result2] = await parallel(someResult(), anotherResult());
I vote for:
await Promise.all([someCall(), anotherCall()]);
Be aware of the moment you call functions, it may cause unexpected result:
// Supposing anotherCall() will trigger a request to create a new User
if (callFirst) {
await someCall();
} else {
await Promise.all([someCall(), anotherCall()]); // --> create new User here
}
But following always triggers request to create new User
// Supposing anotherCall() will trigger a request to create a new User
const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User
if (callFirst) {
await someCall();
} else {
const finalResult = [await someResult, await anotherResult]
}

Resources