Search code examples
javascriptes6-promise

Compose functions which return Promises


I would like to compose two functions which return Promises into a function which returns a Promise.

I really want to do this for asynchronous system calls, but to simplify things, for now I'm playing with Promises wrapped around setTimeout. Most of the system calls can be sent out and returned in any order, but in a few instances, data from one system call is needed to create a second system call.

In the enclosed example, I want to send out alerts with an interval of 5 seconds, then 3 seconds, then 10 seconds. When I run the code, things run in the wrong order.

I commented out code where I was able to run the two calls chained with ".then". The commented out code works as desired. But I haven't yet figured out how to put that into a function which returns a promise, and still have it work.

I'm interested in doing this using plain JavaScript only. After I understand how to do that, I'll look at answers which do the same using a library.

jsfiddle

// Returns a Promise.
// I purposely chose not to use the callback function inside setTimeout in order to demonstrate
// how to combine functions which return Promises, and normal functions which don't.
function sleep(delay, callbackFunction) {
  return new Promise(function(resolve) {
    setTimeout(resolve.bind(null, callbackFunction), delay)
  });
}

// Normal function, does not return a promise.
const myPrint = (message, value) => {
  alert(message + " " + value);
};

// A function combining a promise and then a normal function.
// Returns a Promise.
const sleepPrint = (delay) => {
  return sleep(delay)
    .then(() => {
      myPrint("sleepPrint()", delay);
    });
};

// A function combining two functions which return promises.
// Returns a Promise.
const sleepPrintSleepPrint = (delay1, delay2) => {
  return sleepPrint(delay1)
  .then(sleepPrint(delay2));
};

alert("Begin");
/*
// Run a Pomise function, then a normal function.
const delay = 4000;
sleep(delay)
.then(() => {
  myPrint("init", delay);
});
*/

const firstDelay = 5000;
const secondDelay = 3000;

/*
// Call sleepPrint() which runs a Promise function then a normal function.
// After all that finishes, call another Promise function, than another normal function.
// This code works as desired, but the code at the bottom doesn't!
sleepPrint(firstDelay)
.then(() => {
  return sleep(secondDelay)
})
.then(() => {
  myPrint("After sleepPrint() and sleep()", secondDelay);
});
*/

// Comine two calls to sleepPrint() into a new functiion which returns a Promise, sleepPrintsleepPrint().
// After all that finishes, call another Promise function, than another normal function.
// I want this to have 5 second delay and then a 3 second delay, but they are not running in that order.
const thirdDelay = 10000;
sleepPrintSleepPrint(firstDelay, secondDelay)
.then(() => {
  return sleep(thirdDelay)
})
.then(() => {
  myPrint("After sleepPrintSleepPrint()", thirdDelay);
});

Solution

  • Remember how functions work (in any programming language). This:

    const sleepPrintSleepPrint = (delay1, delay2) => {
      return sleepPrint(delay1)
      .then(sleepPrint(delay2));
    };
    

    Is the same as this:

    const sleepPrintSleepPrint = (delay1, delay2) => {
      var x = sleepPrint(delay2);
      return sleepPrint(delay1)
      .then(x);
    };
    

    What you are doing is calling both functions simultaneously. What I think you intend is to call the second function once then resolves. So you need to do this:

    const sleepPrintSleepPrint = (delay1, delay2) => {
      return sleepPrint(delay1)
      .then(function(){return sleepPrint(delay2)});
    };
    

    Or if you prefer arrow function syntax:

    const sleepPrintSleepPrint = (delay1, delay2) => {
      return sleepPrint(delay1)
      .then(() => sleepPrint(delay2));
    };