How to synchronize a sequence of promises?

Asked
Active3 hr before
Viewed126 times

8 Answers

synchronizesequencepromises
90%

Stack Overflow Public questions & answers , GitLab launches Collective on Stack Overflow ,Stack Overflow en español, Meta Stack Overflow

Let's suppose you have an array of items:

var arr = [...];

Manual Iteration

function processItem(item) {
   // do async operation and process the result
   // return a promise
}

Then, you can do something like this:

function processArray(array, fn) {
   var index = 0;

   function next() {
      if (index < array.length) {
         fn(array[index++]).then(next);
      }
   }
   next();
}

processArray(arr, processItem);

If you wanted a promise returned from processArray() so you'd know when it was done, you could add this to it:

function processArray(array, fn) {
   var index = 0;

   function next() {
      if (index < array.length) {
         return fn(array[index++]).then(function(value) {
            // apply some logic to value
            // you have three options here:
            // 1) Call next() to continue processing the result of the array
            // 2) throw err to stop processing and result in a rejected promise being returned
            // 3) return value to stop processing and result in a resolved promise being returned
            return next();
         });
      }
   } else {
      // return whatever you want to return when all processing is done
      // this returne value will be the ersolved value of the returned promise.
      return "all done";
   }
}

processArray(arr, processItem).then(function(result) {
   // all done here
   console.log(result);
}, function(err) {
   // rejection happened
   console.log(err);
});

If you wanted to do more of the work with promises, you could chain all the promises:

function processArray(array, fn) {
   return array.reduce(function(p, item) {
      return p.then(function() {
         return fn(item);
      });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
   // all done here
}, function(reason) {
   // rejection happened
});

And, since it now seems apparent that you want the final promise result to be an array of data (in order), here's a revision of the previous solution that produces that:

function processArray(array, fn) {
   var results = [];
   return array.reduce(function(p, item) {
      return p.then(function() {
         return fn(item).then(function(data) {
            results.push(data);
            return results;
         });
      });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
   // all done here
   // array of data here in result
}, function(reason) {
   // rejection happened
});

And, if you want to insert a small delay between operations:

function delay(t, v) {
   return new Promise(function(resolve) {
      setTimeout(resolve.bind(null, v), t);
   });
}

function processArrayWithDelay(array, t, fn) {
   var results = [];
   return array.reduce(function(p, item) {
      return p.then(function() {
         return fn(item).then(function(data) {
            results.push(data);
            return delay(t, results);
         });
      });
   }, Promise.resolve());
}

processArray(arr, 200, processItem).then(function(result) {
   // all done here
   // array of data here in result
}, function(reason) {
   // rejection happened
});

The Bluebird promise library has a lot of concurrency controlling features built right in. For example, to sequence iteration through an array, you can use Promise.mapSeries().

Promise.mapSeries(arr, function(item) {
   // process each individual item here, return a promise
   return processItem(item);
}).then(function(results) {
   // process final results here
}).catch(function(err) {
   // process array here
});

Or to insert a delay between iterations:

Promise.mapSeries(arr, function(item) {
   // process each individual item here, return a promise
   return processItem(item).delay(100);
}).then(function(results) {
   // process final results here
}).catch(function(err) {
   // process array here
});

If you're coding in an environment that supports async/await, you can also just use a regular for loop and then await a promise in the loop and it will cause the for loop to pause until a promise is resolved before proceeding. This will effectively sequence your async operations so the next one doesn't start until the previous one is done.

async function processArray(array, fn) {
   let results = [];
   for (let i = 0; i < array.length; i++) {
      let r = await fn(array[i]);
      results.push(r);
   }
   return results; // will be resolved value of promise
}

// sample usage
processArray(arr, processItem).then(function(result) {
   // all done here
   // array of data here in result
}, function(reason) {
   // rejection happened
});

While I realize this factory function is a placeholder function (it doesn't even do anything async), hopefully you can see the style to consider it:

function factory(idx) {
   // create the promise this way gives you automatic throw-safety
   return new Promise(function(resolve, reject) {
      switch (idx) {
         case 0:
            resolve("one");
            break;
         case 1:
            resolve("two");
            break;
         case 2:
            resolve("three");
            break;
         default:
            resolve(null);
            break;
      }
   });
}

If any of these operations were async, then they could just return their own promises which would automatically chain to the one central promise like this:

function factory(idx) {
   // create the promise this way gives you automatic throw-safety
   return new Promise(function(resolve, reject) {
      switch (idx) {
         case 0:
            resolve($.ajax(...));
         case 1:
            resole($.ajax(...));
         case 2:
            resolve("two");
            break;
         default:
            resolve(null);
            break;
      }
   });
}

When you have this body of code:

    return obj.then(function(data) {
       result.push(data);
       return loop(++idx, result);
    }, function(reason) {
       return promise.reject(reason);
    });

The reject handler is not adding any value. You can instead just do this:

    return obj.then(function(data) {
       result.push(data);
       return loop(++idx, result);
    });

Here's a version in the general architecture of your code that tries to incorporate most of these ideas:

function factory(idx) {
   // create the promise this way gives you automatic throw-safety
   return new Promise(function(resolve, reject) {
      switch (idx) {
         case 0:
            resolve("zero");
            break;
         case 1:
            resolve("one");
            break;
         case 2:
            resolve("two");
            break;
         default:
            // stop further processing
            resolve(null);
            break;
      }
   });
}

// Sequentially resolves dynamic promises returned by a factory;
function sequence(factory) {
   function loop(idx, result) {
      return Promise.resolve(factory(idx)).then(function(val) {
         // if resolved value is not null, then store result and keep going
         if (val !== null) {
            result.push(val);
            // return promise from next call to loop() which will automatically chain
            return loop(++idx, result);
         } else {
            // if we got null, then we're done so return results
            return result;
         }
      });
   }
   return loop(0, []);
}

sequence(factory).then(function(results) {
   log("results: ", results);
}, function(reason) {
   log("rejected: ", reason);
});
load more v
88%

The UFT Developer PromiseManager chains all commands into a promise chain. Furthermore, a command such as click is executed after the entire promise tree of the previous command is completed.,By default, every UFT Developer command is synchronized using an internal entity called the PromiseManager.,The promise returned by UFT Developer commands is a custom promise and not the JavaScript built-in promise.,UFT Developer blog posts

list.selectedItems()
   .then(function(items) {
      assert.equal(items.length, 2);
      assert.strictEqual(items[0], "Socks");
      assert.strictEqual(items[1], "Cape");
   });
load more v
72%

This is huge to understand because it probably goes against what you think is happening during this loop (at least, it did for me). When we use it to sequentially resolve promises, the reduce() loop isn’t actually slowing down at all. It’s completely synchronous, doing its normal thing as fast as it can, just like always.,Have anyone noticed that the last Promise is not resolve? So if we have 3 userIds, we will process only first two of them.,Remember, under the hood of reduce(), we’re not waiting for our callback to complete before moving onto the next item. It’s completely synchronous. The same goes for all of these other methods:,Is it possible to wait until all processing is finished before doing something else? Yes. The synchronous nature of reduce() doesn’t mean you can’t throw a party after every item has been completely processed. Look:

Ruling out packages that help make this task easier (like Caolan McMahon’s async library), the most commonly suggested solution for sequentially resolving promises is to use Array.prototype.reduce(). You might’ve heard of this one. Take a collection of things, and reduce them to a single value, like this:

let result = [1, 2, 5].reduce((accumulator, item) => {
   return accumulator + item;
}, 0); // <-- Our initial value.

console.log(result); // 8

But, when using reduce() for our purposes, the setup looks more like this:

let userIDs = [1, 2, 3];

userIDs.reduce((previousPromise, nextID) => {
   return previousPromise.then(() => {
      return methodThatReturnsAPromise(nextID);
   });
}, Promise.resolve());

Or, in a more modern format:

let userIDs = [1, 2, 3];

userIDs.reduce(async (previousPromise, nextID) => {
   await previousPromise;
   return methodThatReturnsAPromise(nextID);
}, Promise.resolve());

Look at the following snippet and notice how the progress of the loop isn’t hindered at all by the promises returned in the callback.

function methodThatReturnsAPromise(nextID) {
   return new Promise((resolve, reject) => {
      setTimeout(() => {

         console.log(`Resolve! ${dayjs().format('hh:mm:ss')}`);

         resolve();
      }, 1000);
   });
}

[1, 2, 3].reduce((accumulatorPromise, nextID) => {

   console.log(`Loop! ${dayjs().format('hh:mm:ss')}`);

   return accumulatorPromise.then(() => {
      return methodThatReturnsAPromise(nextID);
   });
}, Promise.resolve());

In our console:

"Loop! 11:28:06"
"Loop! 11:28:06"
"Loop! 11:28:06"
"Resolve! 11:28:07"
"Resolve! 11:28:08"
"Resolve! 11:28:09"

The promises resolve in order as we expect, but the loop itself is quick, steady, and synchronous. After looking at the MDN polyfill for reduce(), this makes sense. There’s nothing asynchronous about a while() loop triggering the callback() over and over again, which is what’s happening under the hood:

while (k < len) {
   if (k in o) {
      value = callback(value, o[k], k, o);
   }
   k++;
}

With all that in mind, the real magic occurs in this piece right here:

return previousPromise.then(() => {
   return methodThatReturnsAPromise(nextID)
});

Each time our callback fires, we return a promise that resolves to another promise. And while reduce() doesn’t wait for any resolution to take place, the advantage it does provide is the ability to pass something back into the same callback after each run, a feature unique to reduce(). As a result, we’re able build a chain of promises that resolve into more promises, making everything nice and sequential:

new Promise((resolve, reject) => {
   // Promise #1

   resolve();
}).then((result) => {
   // Promise #2

   return result;
}).then((result) => {
   // Promise #3

   return result;
}); // ... and so on!

All of this should also reveal why we can’t just return a single, new promise each iteration. Because the loop runs synchronously, each promise will be fired immediately, instead of waiting for those created before it.

[1, 2, 3].reduce((previousPromise, nextID) => {

   console.log(`Loop! ${dayjs().format('hh:mm:ss')}`);

   return new Promise((resolve, reject) => {
      setTimeout(() => {
         console.log(`Resolve! ${dayjs().format('hh:mm:ss')}`);
         resolve(nextID);
      }, 1000);
   });
}, Promise.resolve());

In our console:

"Loop! 11:31:20"
"Loop! 11:31:20"
"Loop! 11:31:20"
"Resolve! 11:31:21"
"Resolve! 11:31:21"
"Resolve! 11:31:21"

Is it possible to wait until all processing is finished before doing something else? Yes. The synchronous nature of reduce() doesn’t mean you can’t throw a party after every item has been completely processed. Look:

function methodThatReturnsAPromise(id) {
   return new Promise((resolve, reject) => {
      setTimeout(() => {
         console.log(`Processing ${id}`);
         resolve(id);
      }, 1000);
   });
}

let result = [1, 2, 3].reduce((accumulatorPromise, nextID) => {
   return accumulatorPromise.then(() => {
      return methodThatReturnsAPromise(nextID);
   });
}, Promise.resolve());

result.then(e => {
   console.log("Resolution is complete! Let's party.")
});

We found that the reason reduce() works for us is because we’re able to return something right back to our same callback (namely, a promise), which we can then build upon by having it resolve into another promise. With all of these other methods, however, we just can’t pass an argument to our callback that was returned from our callback. Instead, each of those callback arguments are predetermined, making it impossible for us to leverage them for something like sequential promise resolution.

[1, 2, 3].map((item, [index, array]) => [value]);
[1, 2, 3].filter((item, [index, array]) => [boolean]);
[1, 2, 3].some((item, [index, array]) => [boolean]);
[1, 2, 3].every((item, [index, array]) => [boolean]);
load more v
65%

Running code in response to multiple promises fulfilling,Running some final code after a promise fulfills/rejects,You could even do this, since the functions just pass their arguments directly, so there isn't any need for that extra layer of functions:,Try running this live to see the result (also see the source code).

function handleCallButton(evt) {
   setStatusMessage("Calling...");
   navigator.mediaDevices.getUserMedia({
         video: true,
         audio: true
      })
      .then(chatStream => {
         selfViewElem.srcObject = chatStream;
         chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream));
         setStatusMessage("Connected");
      }).catch(err => {
         setStatusMessage("Failed to connect");
      });
}
load more v
75%

Can we make a fetch call instead of the promise and wait for the response.,fetch() has a pretty good interface, but it also uses Promises, so the call would look pretty much the same…,2. Callbacks work just fine, but…, Composing the results of nested asynchronous calls in Javascript

1. The problem

/*
If you are used to writing procedural code in a language like Python, Java, or C++,
you would expect this code to print step1, step2, step3, and so on. Because Javascript
is non-blocking, this isn't what happens at all. The HTTP requests take time to execute,
and so the JS runtime moves the call to a queue and just keeps going. Once all of the calls on the
main portion of the call stack are complete, an event loop visits each of the completed request()s 
in the order they completed and executes their callbacks. 

 Starting demo
 Finished demo
 step3: UHub
 step2: CNN
 step1: KidPub

So what if we need to execute the requests in order, maybe to build up a result from each of them?
*/

var request = require('request');

var step1 = function(req1) {
   request(req1, function(err, resp) {
      console.log('step1: KidPub');
   });
};

var step2 = function(req2) {
   request(req2, function(err, resp) {
      console.log('step2: CNN');
   });
};

var step3 = function(req3) {
   request(req3, function(err, resp) {
      console.log('step3: UHub');
   });
};

console.log('Starting demo');
step1('http://www.kidpub.com');
step2('http://www.cnn.com');
step3('http://universalhub.com');
console.log('Finished demo');
load more v
40%

i have array of promise objects must resolved in same sequence in listed in array, i.e. cannot attempt resolving element till previous 1 has been resolved (as method all([...]) does).,and if 1 element rejected, need chain reject @ once, without attempting resolve following element.,the initial answers suggest can sequence results of such array elements, not execution, because predefined in such example.,but how generate array of promises in such way avoid execution?

how can implement this, or there existing implementation such sequence pattern?

function sequence(arr) {
   return new promise(function(resolve, reject) { // try resolving elements in 'arr',         // strictly 1 after another;     }); } 

here's modified example:

function sequence(nextpromise) { // while nextpromise() creates , returns promise,     // continue resolving it; } 
load more v
22%

TL;DR: A prototypical animal which looks like an A+ Promise but doesn't defer immediately, so can run synchronously, for testing. Technically, this makes it not A+ compliant, since part of the A+ spec is that resolution be asynchronous.,SynchronousPromise looks (from the outside) a lot like an ES6 promise. We construct the same:,You create a SynchronousPromise which is resolved asynchronously, eg:,SynchronousPromise also provides two extra functions to make testing a little easier:

describe('the thing', () => {
         it('will do some stuff', () => { // Arrange    const asyncLibraryFake = {      someMethod: sinon.stub().returns(Promise.resolve('happy value!'))    },    sut = createSystemUnderTestWith(asyncLibraryFake);    // Act    sut.doSomethingInteresting();    // Assert    //  [*]  })});
load more v
60%

You can also start a chain of then() method calls via Promise.resolve() and execute the synchronous code inside a callback:,then() always returns a Promise, which enables you to chain method calls:,In this section, we take a closer look at how Promises can be chained. The result of the method call:,When you chain several Promise method calls, you risk silently discarding errors. For example:

function asyncFunc() {
   return new Promise(
      function(resolve, reject) {
         ···
         resolve(result);···
         reject(error);
      });
}
load more v

Other "synchronize-sequence" queries related to "How to synchronize a sequence of promises?"