Reputation: 2551
Im writing a nodejs controller.
I have a whole set of promises where each of them get data from different queries
I want to start them all (because they take different time to complete) and once they are all finished, THEN I want to start my priceCalc function which uses all the data collected.
I looked at a lot of guides and examples, but I dont understand the promise.all and how it could work in my case?
Also, what if one of them fails, it should return an error? They must all complete for the priceCalc function to start.
(im a newbie to promises, so please make it simple if you can :-)))
var findPrice = new Promise(
(resolve, reject) => {
priceTable.findOne........
var movePrice = function (data) {
priceArray = data......
findPrice
.then(movePrice)
.then(fulfilled => console.log('findPrice FINISHED!!!'))
.catch(error => console.log(error.message));
var findhotdeal = new Promise(
(resolve, reject) => {
hotdealTable.aggregate.........
var movehotdeal = function (data) {
hotdealArray = data;.......
findhotdeal
.then(movehotdeal)
.then(fulfilled => console.log('findhotdeal FINISHED!!!'))
.catch(error => console.log(error.message));
var calculatePrice = function () {
// Use the data from the previous promises once they all finished
}
Upvotes: 0
Views: 1543
Reputation: 135217
Primitive Promises
I'm glad to see you've learned about Promise.all
, but I think it's important that you can reason about how Promises work and understand that you can build these things on your own.
Your original criteria
I have a whole set of promises where each of them get data from different queries
I want to start them all (because they take different time to complete) and once they are all finished, THEN I want to start my priceCalc function which uses all the data collected.
We wish for our code to look something like below
all(arrayOrPromises)
.then(values => onSuccess(values), err => onFailure(err))
I'll start by implementing all
and explaining a little bit of it. Then, I'll introduce a couple other helpers to build a demo to ensure everything is working as intended.
So intuitively, all
takes an array of promises, and returns a promise that resolves an array of values.
// all :: [Promise a] -> Promise [a]
const all = ps =>
new Promise((resolve, reject) => {
let pending = ps.length, result = []
ps.forEach((p,i) => p
.then(x => result[i] = x)
.then(() => --pending === 0 ? resolve(result) : null)
.catch(reject))
})
We first start by returning a Promise. When given an input array of N promises, we know we need N promises to resolve before we can finally resolve the promise returned by all
. So all we're doing is creating a container for the resolved values (result
), attaching a .then
to each promise that stores the resolved value, and attaching one more .then
to check if we can finally resolve the result
– when pending
is 0
, there are no more pending promises, so it's time to resolve
. Lastly, I'm attaching a .catch
to each promise so that the outer promise is rejected immediately in the event that any promise fails.
Demo helpers
To construct a demo, we need to create an array of promises that take a random amount of time to complete. Of course your code will be calling actual functions that return promises, so this is just a mock of that – the reason I'm including these is so that all
can be exhibited here in this answer as an isolated, standalone demo.
// fakeP :: a -> Promise a
const fakeP = x =>
Promise.resolve(x)
.then(log('start'))
.then(wait(rand(5000)))
.then(log('end'))
fakeP
makes a a promise with a "fake" delay (random 0-5 seconds). For added visualisation, fakeP
will also log when a promise starts and ends
// eg
fakeP(1).then(console.log, console.error)
// start 1
// <random delay>
// end 1
// 1
log
, wait
, and rand
are not interesting but you can read their definition in the code below
Runnable demo
Click Run below to see the output. Most importantly, note that although the promises resolve in a nondeterministic order, the resolved values will appear in order that the promises were passed in
// all :: [Promise a] -> Promise [a]
const all = ps =>
new Promise((resolve, reject) => {
let pending = ps.length, result = []
ps.forEach((p,i) => p
.then(x => result[i] = x)
.then(() => --pending === 0 ? resolve(result) : null)
.catch(reject))
})
// fakeP :: a -> Promise a
const fakeP = x =>
Promise.resolve(x)
.then(log('start'))
.then(wait(rand(5000)))
.then(log('end'))
// wait :: Int -> a -> Promise a
const wait = ms => x =>
new Promise(r => setTimeout(r, ms, x))
// label :: String -> a -> a
const log = label => x =>
(console.log(label, x), x)
// rand :: Number -> Number
const rand = x =>
Math.random() * x
// 5 "fake" promises with random delay (0-5 seconds each)
const promises =
[ fakeP('a'), fakeP('b'), fakeP('c'), fakeP('d'), fakeP('e') ]
// run it
all(promises).then(console.log, console.error)
// start a
// start b
// start c
// start d
// start e
// end e
// end a
// end d
// end c
// end b
// [ 'a', 'b', 'c', 'd', 'e' ]
So in the example here, we're just using console.log
as the onSuccess
function
all(promises).then(console.log, console.error)
// ...
// [ 'a', 'b', 'c', 'd', 'e' ]
We could replace that with anything to do whatever we'd like with our values
all(promises).then(
xs => console.log(xs.reverse().join('').toUpperCase()),
console.error
)
// ...
// EDCBA
Remarks
Ok, so don't use all
when Promise.all
is precisely what you need and already provided for you. My point in writing this post though is that you don't have to feel helpless when you have a task and you're unaware of a all-in-one function that magically solves all of your problems.
There's nothing wrong with writing your own function to solve your problem and then later returning to your code once you learn Promise.all
exists.
Upvotes: 1
Reputation: 1
If you need the resolved values of all 4 promises
var findPrice = new Promise(
(resolve, reject) => {
priceTable.findOne........
var movePrice = function (data) {
priceArray = data......
var findhotdeal = new Promise(
(resolve, reject) => {
hotdealTable.aggregate.........
var movehotdeal = function (data) {
hotdealArray = data;.......
var calculatePrice = function ([fp, mp, fh, mh]) {
// fp == resolved value of findPrice
// mp == resolved value of movePrice
// fh == resolved value of findhotdeal
// mh == resolved value of movehotdeal
}
Promise.all([findPrice, findPrice.then(movePrice), findhotdeal, findhotdeal.then(movehotdeal)])
.then(calculatePrice)
.catch(err => { /* handle errors here */})
Note:
var calculatePrice = function ([fp, mp, fh, mh]) {
// fp == resolved value of findPrice
// mp == resolved value of movePrice
// fh == resolved value of findhotdeal
// mh == resolved value of movehotdeal
}
is ES2015+ shorthand for
var calculatePrice = function (r) {
var fp = r[0],
mp = r[1],
fh = r[2],
mh = r[3];
// fp == resolved value of findPrice
// mp == resolved value of movePrice
// fh == resolved value of findhotdeal
// mh == resolved value of movehotdeal
}
Given that movePrice and movehotdeal are not asyncrhonous
var findPrice = new Promise((resolve, reject) => {
priceTable.findOne........
});
var findhotdeal = new Promise((resolve, reject) => {
hotdealTable.aggregate.........
});
var calculatePrice = function ([fp, fh]) {
priceArray = fp;
hotdealArray = fh;
// fp == resolved value of findPrice
// fh == resolved value of findhotdeal
}
Promise.all([findPrice, findhotdeal])
.then(calculatePrice)
.catch(err => { /* handle errors here */})
Upvotes: 3