best practice for nesting promises in succession - javascript

I currently have a few functions that return promises such as the below:
function action_one(){
return new Promise((resolve, reject)->{
...
});
}
I want to be able to wait on one before doing the next promise without nesting them. I had searched for some solutions, one being PromiseAll() but it does not seem to do it in order. Another solution was to do the following:
promise.then(result =>{
action
}.then(result =>{
action
}.then(result =>{
action
}.then(result =>{
action
}.then(result =>{ etc.
which I'm not sure works for my issue but it doesn't seem compactable.
what would be best practice in this situation?
Edit:
I'm not sure how to use the .then chain with multiple promises such as:
promise.then(result =>{
action
}promise.then(result =>{
action
}promise.then(result =>{
action
}promise.then(result =>{
action
}promise.then(result =>{ etc.
It does not yield the result I expect

I found my solution:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
I needed to return the promise to chain them which is why it was not working.
Source: https://javascript.info/promise-chaining

Related

Can I have multiple .finally() as well as multiple .catch() in Promise.all()?

var p1 = new Promise((resolve, reject) => {
resolve('p1');
});
var p2 = new Promise((resolve, reject) => {
resolve('p2');
});
Promise.all([
p1.finally(() => { console.log("p1 completed"); }),
p2.finally(() => { console.log("p2 completed"); }),
]).then(values => {
console.log(values[0]);
console.log(values[1]);
}).finally(() => {
console.log("all() completed");
I think I've only seen examples on the web with a single .finally() at the end
[1]: https://i.stack.imgur.com/HeQV8.png
Sure, finally is a chainable promise method just like catch (with the only difference that its callback does not take a parameter). You can use it as many times as you want, on any promise.
Promise.all([
p1.finally(() => { console.log("p1 completed"); }),
p2.finally(() => { console.log("p2 completed"); }),
]).finally(() => {
console.log("all() completed");
})
You may chain as many .finally() calls as you like onto any promise.
(Promise.all() returns a new promise, so this rule applies here as well.)
Run this, and you should see all 3 comments log.
Promise.resolve().
finally(() => console.log('Finally #1')).
finally(() => console.log('Finally #2')).
finally(() => console.log('Finally #3'))

Page rendering before Promise resolves in Node, Express, mongoDB

I have removed error handling code as it is working (for simplicicty).
I have a function createOrder() which creates a new record in my MongoDB collection:
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order});
resolve();
});
getOrders() function that adds all current orders in the database to an array.
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
});
resolve();
});
An external javascript file to post an order:
postOrder = () => {
window.location = `/new_order/order=${JSON.stringify(itemArray)}`;
}
and the node application that "gets" the request:
app.get("/new_order/order=:order", (req, res) => {
createOrder(JSON.parse(req.params.order))
.then(getOrders)
.then(_ => res.render("userprofile", {user : curUser, orders: curUserOrders}));
});
when i post a new order the postOrder() function is triggered and "userprofile" is displayed but none of the orders are displayed until i refresh the page again. Im still learning how promises work so im guessing the problem lies there. Please help :).
Promises are used for asynchronous operations, but your example resoves the promise right away.
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order});
resolve();
});
You would write the create function like:
createOrder(order).then(()=>{
// The asynchronous operation is completed!
})
But in your case it would happen the following:
createOrder = order => new Promise((resolve, reject) => {
//Start the asynchronous operation ....
Order.create({orderId: order});
//... but you just resolve the operation right away.
resolve();
});
A better example is probably the other function:
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
});
// You just resolve without waiting for the callback of find.
resolve();
});
What you want:
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
// Here we call resolve inside the callback and we wait for the find method and return the orders to the getOrders().then(orders => {}) call.
resolve(orders);
});
});
A Promise is what the name suggests, it provides you with a promise, that it will be fulfilled (resolved) at some point in the future. So you always want to wait with the resolve function until all operations are completed without errors.
Use it as callback like this
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order}).then(()=>{resolve});
});

Promise.all.then is executed before the individual promises are completed

The following sample code represents what am trying to do, and am stumped as to why the console.log inside the Promises.all.then() is completed before even the individual promises are completed. I am thinking that the map statement has something to do with it , may be, but am unsure.
My intention of using a map was to collect all successes and or failures and notify the user later on bulk operations.
var requests = []; var success=[]; var failures = [];
requests.push(new Promise((resolve, reject) => setTimeout(() => resolve("foo success"), 2000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => reject("bar failure"), 1000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => resolve("foo2 success"), 2000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => reject("bar2 failure"), 1000)));
Promise.all(requests.map(p => {
p.then(r => {
console.log(r)
success.push(r);
});
p.catch((e) => { console.log(e); failures.push(e); });
})).then(function () { console.log("ALL promises completed")})
May i know what is wrong with the above code? Am i not implementing the promises as intended.?
It's because your requests.map(...) callback doesn't return anything, so you're mapping the Promises to undefined values. Therefore, you're awaiting an array of undefined values with the Promise.all call. Change to the following:
Promise.all(requests.map(p => {
p.then(r => {
console.log(r)
console.log("POSTed a record successfuly");
success.push(r);
});
p.catch((e) => { console.log(e); failures.push(e); });
return p;
})).then(function () { console.log("POSTED ALL")})
Update:
As #JoeFrambach pointed out, if you want the Promise.all to contain the contents of the original requests Promises, you'll want to return the original Promises. Since that's most likely what you want to do, I've updated my answer.
just return promise from your map callback function
var requests = []; var success=[]; var failures = [];
requests.push(new Promise((resolve, reject) => setTimeout(() => resolve("foo success"), 2000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => reject("bar failure"), 1000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => resolve("foo2 success"), 2000)));
requests.push(new Promise((resolve, reject) => setTimeout(() => reject("bar2 failure"), 1000)));
Promise.all(requests.map(p => {
p.then(r => {
console.log(r)
success.push(r);
});
p.catch((e) => { console.log(e); failures.push(e); });
return p; // ADD THIS LINE
})).then(function () { console.log("ALL promises completed")})
.catch(function() {console.log("FAILED PROMISE ALL")});
You need to return a promise within .map function.
Within .map function make last line as return p.
Promise all expects array of promises. You are returning array of undefined from map function so it immediately gets resolved.

What happens if you don't return a promise and only use resolve?

In other words, what happen if I do this:
const setBuildingPayload = new Promise((resolve, reject) => {
this.buildingForm.schema.forEach(() => {
setTimeout(() => {
resolve()
}, 3000)
})
})
Instead of this?
const setBuildingPayload = new Promise((resolve, reject) => {
return this.buildingForm.schema.map(() => {
return setTimeout(() => {
resolve()
}, 3000)
})
})
I ask this because I was surprised when using setBuildingPayload in Promise.all without return worked (the code inside Promise.all triggered after resolve().
And I thought every promise should return its value.
The function in question, (resolve, reject) => {...}, is called an executor function for the Promise. As per the last sentence in MDN docs for the executor
The return value of the executor is ignored
This can be demonstrated using this simple piece of code (modified from the example provided)
const setBuildingPayload = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Done");
}, 1000);
return 100;
});
setBuildingPayload.then(result => {
console.log(result);
});
Notice how return 100 didn't make any difference. Hence, the returns in your second piece of code make no difference as well.
The return this.buildingForm.schema.map has no impact on how the Promise resolves. Its an unused return value and what our team likes to call "for funsies".

How to structure nested Promises

I have a situation where I think the only choice for me is to nest some Promises within each other. I have a Promise that needs to be performed and a method that does something until that Promise is complete. Something like this:
let promise = new Promise((resolve, reject) => {
// Do some stuff
});
doSomethingUntilPromiseisDone(promise);
However, within my Promise, I need to execute another method that returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
// Do something here
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
But now, in the fetchValue method's then statement, I have another method I need to execute that, guess what, returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
saveToCache(value)
.then((success) => {
console.log('success!!');
resolve('success');
});
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
So in the end, I have a Promise, within a Promise, within a Promise. Is there someway I can structure this better so that it is more straightforward? It seems like nesting them within each other is counter to Promise's intended chaining approach.
Use .then()
let doStuff = (resolve, reject) => {/* resolve() or reject() */};
let promise = new Promise(doStuff);
doSomethingUntilPromiseisDone(
promise
.then(value => fetchValue(url))
.then(value => value.blob())
.then(saveToCache)
)
.then(success => console.log("success!!"))
.catch(err => console.error(err))
you can use generator to flatten your nested promises (Bluebird.couroutine or Generators)
//Bluebird.couroutine
const generator = Promise.coroutine(function*() {
try {
const value = yield fetchValue(url);
const success = yield saveToCache(value);
console.log('success:', success);
} catch(e) {
console.error(err);
}
}));
generator();
Each function will call the next one with the result of the method before.
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);

Categories