Juicy
Juicy

Reputation: 12500

Run a callback after multiple function have completed

I have multiple time-consuming functions, and I want to run a function after they have all completed, for example:

data.x = thisTakes2Seconds();
data.y = thisTakes5Seconds();
http.post(data);

I'm familiar with the concept of callbacks in Javascript, but if I have several functions, am I really supposed to have nested callbacks several functions deep?

Upvotes: 4

Views: 2502

Answers (3)

Marcos Casagrande
Marcos Casagrande

Reputation: 40374

For handling asynchronous functions easily, the best way is to use promises, and async/await

function thisTakes2Seconds() {
  return new Promise(resolve => setTimeout(() => resolve(3), 200)); // 0.2 to avoid waiting :P
}

function thisTakes5Seconds() {
  return new Promise(resolve => setTimeout(() => resolve(5), 500));
}

async function foo() {
  const data = {};
  
  data.x = await thisTakes2Seconds();
  data.y = await thisTakes5Seconds();
  
  // This will run once both promises have been resolved
  console.log(data);
}

foo()
  .then(() => console.log('done!')
  .catch(err => console.error(err));

If you wish to perform both functions in parallel, you can do so, and wait for both to finish using Promise.all

async function foo() {
  const data = {};

  // Promise.all returns an array where each item is the resolved
  // value of the promises passed to it, maintaining the order
  // So we use destructuring to assign those values
  [data.x, data.y] = await Promise.all([
    thisTakes2Seconds(),
    thisTakes5Seconds()
  ]);

  console.log(data);
}

If you already have an async function using callbacks, you can easily convert it to promises.

function myAsyncFunction(callback) {
    setTimeout(() => {
        callback(Math.random());
    }, 200);
}

function myAsyncFunctionPromise() {
     return new Promise((resolve, reject) => {
         myAsyncFunction(resolve);
         // If there is an error callback, just pass reject too.
     });
}

There are libraries like bluebird, that already have an utility method to promisify callback API.

http://bluebirdjs.com/docs/api/promise.promisify.html


If you're running it on the browser, and need to support outdated ones, you can use babel to transpile async/await to ES5

Upvotes: 2

T.J. Crowder
T.J. Crowder

Reputation: 1073968

Your thisTakesXSeconds functions are immediately returning their results. That tells us that they're synchronous. No need for callbacks, that code will just take ~7 seconds to run.


If thisTakesXSeconds started an asynchronous process that took X seconds (although the fact you're return a result suggests otherwise), we'd look at ways of managing the completion process.

am I really supposed to have nested callbacks several functions deep?

That question, and the general dissatisfaction with the answer "yes," is why we now have promises and even async functions. :-)

You'd make your thisTakesXSeconds functions return a promise, and then do something along these lines if the functions can run in parallel:

Promise.all([
    thisTakes2Seconds(),
    thisTakes5Seconds()
])
.then(([x, y]) => {
    data.x = x;
    data.y = y;
    // use or return `data` here
})
// return the promise or add a `catch` handler

If they need to run in series (one after another), then

thisTakes2Seconds()
    .then(x => {
        data.x = x;
        return thisTakes5Seconds();
    })
    .then(y => {
        data.y = y;
        // use or return `data` here
    })
    // return the promise or add a `catch` handler

...which looks a bit clearer in an async function:

data.x = await thisTakes2Seconds();
data.y = await thisTakes5Seconds();
// use or return `data` here
// add appropriate error handling (at this level or when calling the function)

Upvotes: 1

Nathan Hinchey
Nathan Hinchey

Reputation: 1201

One technique I have used to handle executing some code after several async calls have executed is to use a "has completed" counter or object.

Each function executes a callback that includes

if (counter == numberOfFuntionsIWantedToComplete) 
    doTheAfterWeHaveAllDataThing`

Upvotes: 0

Related Questions