Condividi tramite


Using Map/Reduce with Asynchronous Operations in TypeScript

A common problem I've encountered is the need to work with a series of operations, all asynchronous, while maintaining ordering but knowing when the series is complete. To that end I wanted to share a pattern I've come to like and should be applicable across many types of scenarios.

First we start with an array of operations that will result in a Promise being returned. Likely an XHR request, but could also be a custom operation, or a file read in node.

 
function something(a: number): Promise<any> {
   // ...
}

let operations: Promise<any>[] = [];
operations.push(something(1).then(a => {...}));
operations.push(something(2).then(a => {...}));
operations.push(something(3).then(a => {...}));

Now that we have a set of operations we need to let them finish, but be alerted when they are done. We can do this with the array's reduce method.

 operations.reduce((chain, operation) => {
 return chain.then(_ => operation);
}, Promise.resolve()).then(_ => { // All the operations are complete });

The initial value, given by Promise.resolve(), allows us to create a chain of promises that will resolve in order and let us know when the entire set if complete. There are other parameters you get in the reduce delegate signature: index and the target array, see the docs for full details. You can also use the map method to handle any pre-processing you might need in your array of operations.

 operations.map(operation => {

  // do something to operation
  return operation;

}).reduce((chain, operation) => {

  return chain.then(_ => operation);

}, Promise.resolve()).then(_ => {

  // all the operations have finished
});

You can see this pattern in action in the JavaScript Core Libraries batching to reduce the batched operations into a single promise to allow the caller to know when the batched requests are all complete.

Hope folks find this helpful when working to resolve a set of operations which all return a promise.