Node JS: chaining promises which are using promises - javascript

I need to chain promises which are using request promises, so it is kinda chaining nested promises.
Imagine the code:
const rp = require('request-promise');
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
methodA();
}
};
function methodA(){
let options = {...};
rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
};
function methodB(resultA){
let options = {uri: resultA};
rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
};
function methodC(resultA, resultB){
//some calculations
};
In doStuff I need to wait for result of all ten executions of methodC and collect them into array. I have tried to chain it like that:
function doStuff(){
for ( let i = 0; i <= 10; i++ ){
let promiseA = new Promise((resolve, reject) => {
resolve(methodA());
});
let promiseB = promiseA.then(result => methodB(result));
let promiseC = promiseB.then(result => methodC(promiseA.result, result));
Promise.all([promiseA, promiseB, promiseC]);
}
};
But for sure it won't work, because in methodA and methodB we have HTTP requests which are asynchronous. Therefore, result in promiseB is undefined.
It means, the question is: how to chain promises, if they have nested promises? (and how to collect result in the end?)
Thanks!
UPDATE: Chaining promises also not much of the help, as 1 is returned prior array of AB's, but desired result is vice versa:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
Promise.all(promises).then(results => {
console.log(results);
});
return 1;
}
console.log(doStuff());

Each of your functions needs to return their promise:
function methodA(){
let options = {...};
return rp(options)
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let options = {uri: resultA};
return rp(options)
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
//some calculations
}
function doStuff() {
let promises = [];
for ( let i = 0; i <= 10; i++ ){
promises.push(methodA());
}
Promise.all(promises).then(...)
}
Edit: I created a test example, which creates promises in methodA and methodB. Each promise lasts some amount of time from 0 to 2 seconds. It seems to be working:
function methodA(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved A');
resolve('A');
}, Math.random() * 2000);
});
return promise
.then(result => methodB(result))
.catch(err => console.log(err));
}
function methodB(resultA){
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Resolved B');
resolve('B');
}, Math.random() * 2000);
});
return promise
.then(result => methodC(resultA, result))
.catch(err => console.log(err));
}
function methodC(resultA, resultB){
return resultA + resultB;
}
function doStuff() {
let promises = [];
for (let i = 0; i <= 10; i++){
promises.push(methodA());
}
return Promise.all(promises).then(results => {
console.log(results);
return 1;
});
}
doStuff().then(result => {
console.log(result);
});

Answer by #Frank_Modica is the way to go.
Just want to add that if you enable async/await you could do it like this. But it does require Babel or Typescript:
async function myMethod() {
const options = { }
try {
const result_a = await rp(options)
const result_b = await rp({ uri: result_a })
const result_c = ...
} catch(err) {
console.log(err)
}
}
for (let i = 0; i <= 10; i++) {
await myMethod();
}

It has to be lightly changed to:
Promise.all([promiseA, promiseB, promiseC]).then([promiseD]);
Also in the functions itself it has to be a return statement to make them chained. For the request itself, just add: {async: false}
Also it can be used:
var runSequence = require('run-sequence');
function methodA () {
return runSequence(
'promiseA',
'promiseB',
['promiseC1', 'promiseC2'],
'promiseD',
callback
);
}

Related

how to get the value in then catch [duplicate]

I have a loop which calls a method that does stuff asynchronously. This loop can call the method many times. After this loop, I have another loop that needs to be executed only when all the asynchronous stuff is done.
So this illustrates what I want:
for (i = 0; i < 5; i++) {
doSomeAsyncStuff();
}
for (i = 0; i < 5; i++) {
doSomeStuffOnlyWhenTheAsyncStuffIsFinish();
}
I'm not very familiar with promises, so could anyone help me to achieve this?
This is how my doSomeAsyncStuff() behaves:
function doSomeAsyncStuff() {
var editor = generateCKEditor();
editor.on('instanceReady', function(evt) {
doSomeStuff();
// There should be the resolve() of the promises I think.
})
}
Maybe I have to do something like this:
function doSomeAsyncStuff() {
var editor = generateCKEditor();
return new Promise(function(resolve,refuse) {
editor.on('instanceReady', function(evt) {
doSomeStuff();
resolve(true);
});
});
}
But I'm not sure of the syntax.
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();
Sample output (because of the Math.random, what finishes first may vary):
Resolving 3
Resolving 2
Resolving 1
Resolving 4
Resolving 0
All done [0,1,2,3,4]
A reusable function works nicely for this pattern:
function awaitAll(count, asyncFn) {
const promises = [];
for (i = 0; i < count; ++i) {
promises.push(asyncFn());
}
return Promise.all(promises);
}
OP example:
awaitAll(5, doSomeAsyncStuff)
.then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
.catch(e => console.error(e));
A related pattern, is iterating over an array and performing an async operation on each item:
function awaitAll(list, asyncFn) {
const promises = [];
list.forEach(x => {
promises.push(asyncFn(x));
});
return Promise.all(promises);
}
Example:
const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];
function doSomeAsyncStuffWith(book) {
return Promise.resolve(book.name);
}
awaitAll(books, doSomeAsyncStuffWith)
.then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
.catch(e => console.error(e));
/*** Worst way ***/
for(i=0;i<10000;i++){
let data = await axios.get(
"https://yourwebsite.com/get_my_data/"
)
//do the statements and operations
//that are dependant on data
}
//Your final statements and operations
//That will be performed when the loop ends
//=> this approach will perform very slow as all the api call
// will happen in series
/*** One of the Best way ***/
const yourAsyncFunction = async (anyParams) => {
let data = await axios.get(
"https://yourwebsite.com/get_my_data/"
)
//all you statements and operations here
//that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends
//=> this approach will perform very fast as all the api call
// will happen in parallal
const doSomeAsyncStuff = async (funcs) => {
const allPromises = funcs.map(func => func());
return await Promise.all(allPromises);
}
doSomeAsyncStuff([
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);
Here is code that I wrote for myself in order to understand the answers stated here. I have mongoose queries in a for loop, so I put here the asyncFunction to take its place. Hope it helps anyone. You can run this script in node or any of many Javascript runtimes.
let asyncFunction = function(value, callback)
{
setTimeout(function(){console.log(value); callback();}, 1000);
}
// a sample function run without promises
asyncFunction(10,
function()
{
console.log("I'm back 10");
}
);
//here we use promises
let promisesArray = [];
let p = new Promise(function(resolve)
{
asyncFunction(20,
function()
{
console.log("I'm back 20");
resolve(20);
}
);
});
promisesArray.push(p);
for(let i = 30; i < 80; i += 10)
{
let p = new Promise(function(resolve)
{
asyncFunction(i,
function()
{
console.log("I'm back " + i);
resolve(i);
}
);
});
promisesArray.push(p);
}
// We use Promise.all to execute code after all promises are done.
Promise.all(promisesArray).then(
function()
{
console.log("all promises resolved!");
}
)
Here's an elegant solution for you if you want to do the same thing multiple times:
await Promise.all(new Array(10).fill(0).map(() => asyncFn()));
This creates an array with 10 items, fills it with zeros and then maps it to an array of promises.

.then after promise is execute before promise is finished [duplicate]

I have a loop which calls a method that does stuff asynchronously. This loop can call the method many times. After this loop, I have another loop that needs to be executed only when all the asynchronous stuff is done.
So this illustrates what I want:
for (i = 0; i < 5; i++) {
doSomeAsyncStuff();
}
for (i = 0; i < 5; i++) {
doSomeStuffOnlyWhenTheAsyncStuffIsFinish();
}
I'm not very familiar with promises, so could anyone help me to achieve this?
This is how my doSomeAsyncStuff() behaves:
function doSomeAsyncStuff() {
var editor = generateCKEditor();
editor.on('instanceReady', function(evt) {
doSomeStuff();
// There should be the resolve() of the promises I think.
})
}
Maybe I have to do something like this:
function doSomeAsyncStuff() {
var editor = generateCKEditor();
return new Promise(function(resolve,refuse) {
editor.on('instanceReady', function(evt) {
doSomeStuff();
resolve(true);
});
});
}
But I'm not sure of the syntax.
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();
Sample output (because of the Math.random, what finishes first may vary):
Resolving 3
Resolving 2
Resolving 1
Resolving 4
Resolving 0
All done [0,1,2,3,4]
A reusable function works nicely for this pattern:
function awaitAll(count, asyncFn) {
const promises = [];
for (i = 0; i < count; ++i) {
promises.push(asyncFn());
}
return Promise.all(promises);
}
OP example:
awaitAll(5, doSomeAsyncStuff)
.then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
.catch(e => console.error(e));
A related pattern, is iterating over an array and performing an async operation on each item:
function awaitAll(list, asyncFn) {
const promises = [];
list.forEach(x => {
promises.push(asyncFn(x));
});
return Promise.all(promises);
}
Example:
const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];
function doSomeAsyncStuffWith(book) {
return Promise.resolve(book.name);
}
awaitAll(books, doSomeAsyncStuffWith)
.then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
.catch(e => console.error(e));
/*** Worst way ***/
for(i=0;i<10000;i++){
let data = await axios.get(
"https://yourwebsite.com/get_my_data/"
)
//do the statements and operations
//that are dependant on data
}
//Your final statements and operations
//That will be performed when the loop ends
//=> this approach will perform very slow as all the api call
// will happen in series
/*** One of the Best way ***/
const yourAsyncFunction = async (anyParams) => {
let data = await axios.get(
"https://yourwebsite.com/get_my_data/"
)
//all you statements and operations here
//that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends
//=> this approach will perform very fast as all the api call
// will happen in parallal
const doSomeAsyncStuff = async (funcs) => {
const allPromises = funcs.map(func => func());
return await Promise.all(allPromises);
}
doSomeAsyncStuff([
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
() => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);
Here is code that I wrote for myself in order to understand the answers stated here. I have mongoose queries in a for loop, so I put here the asyncFunction to take its place. Hope it helps anyone. You can run this script in node or any of many Javascript runtimes.
let asyncFunction = function(value, callback)
{
setTimeout(function(){console.log(value); callback();}, 1000);
}
// a sample function run without promises
asyncFunction(10,
function()
{
console.log("I'm back 10");
}
);
//here we use promises
let promisesArray = [];
let p = new Promise(function(resolve)
{
asyncFunction(20,
function()
{
console.log("I'm back 20");
resolve(20);
}
);
});
promisesArray.push(p);
for(let i = 30; i < 80; i += 10)
{
let p = new Promise(function(resolve)
{
asyncFunction(i,
function()
{
console.log("I'm back " + i);
resolve(i);
}
);
});
promisesArray.push(p);
}
// We use Promise.all to execute code after all promises are done.
Promise.all(promisesArray).then(
function()
{
console.log("all promises resolved!");
}
)
Here's an elegant solution for you if you want to do the same thing multiple times:
await Promise.all(new Array(10).fill(0).map(() => asyncFn()));
This creates an array with 10 items, fills it with zeros and then maps it to an array of promises.

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

Why does async array map return promises, instead of values

See the code below
var arr = await [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
console.log(arr); // <-- [Promise, Promise, Promise ....]
// i would expect it to return [1,2,3,4,5]
Quick edit:
The accepted answer is correct, by saying that map doesnt do anything special to async functions. I dont know why i assumed it recognizes async fn and knows to await the response.
I was expecting something like this, perhaps.
Array.prototype.mapAsync = async function(callback) {
arr = [];
for (var i = 0; i < this.length; i++)
arr.push(await callback(this[i], i, this));
return arr;
};
var arr = await [1,2,3,4,5].mapAsync(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
// outputs 1, 2 ,3 ... with 1 second intervals,
// arr is [1,2,3,4,5] after 5 seconds.
Because an async function always returns a promise; and map has no concept of asynchronicity, and no special handling for promises.
But you can readily wait for the result with Promise.all:
try {
const results = await Promise.all(arr);
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
(async() => {
try {
console.log(await Promise.all(arr));
// Use `results`, which will be an array
} catch (e) {
// Handle error
}
})();
.as-console-wrapper {
max-height: 100% !important;
}
or using Promise syntax
Promise.all(arr)
.then(results => {
// Use `results`, which will be an array
})
.catch(err => {
// Handle error
});
Live Example:
var arr = [1,2,3,4,5].map(async (index) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Promise.all(arr)
.then(results => {
console.log(results);
})
.catch(err => {
// Handle error
});
.as-console-wrapper {
max-height: 100% !important;
}
Side note: Since async functions always return promises, and the only thing you're awaiting in your function is a promise you create, it doesn't make sense to use an async function here anyway. Just return the promise you're creating:
var arr = [1,2,3,4,5].map((index) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(index);
console.log(index);
}, 1000);
});
});
Of course, if you're really doing something more interesting in there, with awaits on various things (rather than just on new Promise(...)), that's different. :-)
Since it is async, the values have not been determined at the time map returns. They won't exist until the arrow function has been run.
This is why Promises exist. They are a promise of a value being available in the future.

How to properly return value in async function when there are lot of callbacks

I have an async function, that calls other async functions, and when they are all done, I'm returning the results.
I don't wanna use Promise.all, because in case any of these functions fail, I just don't add them to my results.
ATM my code looks like this. It works, but I don't like the new Promise, I wanna do it in ES6 async way, so the callAll function should look like const callAll = async (query) => {
const callAll = (query) => {
return new Promise((resolve, reject) => {
const results = [];
const jobs = [
{
promise: someModuleFirst.search(query),
done: false
},
{
promise: someModuleSecond.search(query),
done: false
},
{
promise: someModuleThird.search(query),
done: false
}
];
const areAllDone = () => {
if(!jobs.filter((job) => !job.done).length) {
return true;
}
};
jobs.forEach((job) => {
job.promise.then((result) => {
job.done = true;
results.push(result);
if(areAllDone()) {
resolve(results);
}
}).catch((error) => {
job.done = true;
if(areAllDone()) {
resolve(results);
}
});
});
});
};
You could probably reduce your code to the following. You can return false from the catch handler, then filter out the data, which won't get passed into the result set.
const callAll = async (query) => {
const modules = [someModuleFirst, someModuleSecond, someModuleThird];
const jobs = modules.map((module) => module.search(query).catch(() => false);
const results = await Promise.all(jobs);
return results.filter(Boolean);
};
You can use Promise.all the only thing you need to do is to cache rejection and turn it into resolve.
function someAsyncFunction() {
return new Promise((resolve, reject) => {
setTimeout(function () {
if (Math.round(Math.random() * 100) % 2) {
return resolve('some result');
}
reject('some rejection');
})
}, 1);
}
var promises = [];
for (var i = 0; i < 10; i++) {
// this is important part
// catch block returns a promise with is resolved
// so all promises now will resolve
promises.push(someAsyncFunction().catch(function (reason) {
return reason;
}));
}
Promise.all(promises).then(function (results) {
console.log('All resolved');
console.log(results);
}).catch(function (reason) {
console.error('Rejected');
console.log(reason);
});
So in your case you need to change someModuleFirst.search(query) into some thing like this someModuleFirst.search(query).catch(e => e)

Categories