torbenrudgaard
torbenrudgaard

Reputation: 2551

Run 3 promises, then run the last promise once the others finish

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

Answers (2)

Mulan
Mulan

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

Jaromanda X
Jaromanda X

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

Related Questions