Search code examples
javascriptasync-awaitgeneratorsequencedeferred

Is there a method/function in JS that fires promises one by one, synchronously?


I mean, is there something like Promise.all, only where the methods are executed one after the other, in turn. Or is it left to make your own custom method?

const getUsersIds = (): Promise<any> => API.Users.getUsersIds().then(action((res) => (state.request.userIds = res))); 

const getUsers = (): Promise<any> => API.Users.getUsers(state.request).then(action((res) => (state.users = res))); 



Promise.all([getUsersIds, getUsers]) // - general row

In this case, the main order is, I have to wait until getUsersIds (1) is executed, then based on the ids of these users, get the user objects using the getUsers (2) method


Solution

  • OP

    ... the main question is, is there such a function in JS, I don’t always want to write an async / await function, I want something shorter and more convenient

    Isn't it then, what the OP is looking for, the generic handling of an unknown number of promises (asynchronous functions) in a deferred sequence?

    If this is the case one can achieve such a task by e.g. turning a list/array of promises into an async generator. The implementation should look similar to this ...

    async function* createDeferredValuesPool(asyncFunctions) {
      let asyncFct;
      while (asyncFct = asyncFunctions.shift()) {
    
        yield (await asyncFct());
      }
    }
    

    The usage would look similar to ...

    const deferredValuesPool =
      createDeferredValuesPool([promise1, asynFunction2, asynFunction3]);
    
    for await (const value of deferredValuesPool) {
      // do something e.g. based on `value`
    }
    

    Running example code in order to prove what was said/proposed above.

    // a just example specific helper which creates async functions.
    function createDeferredValueAction(value, delay) {
      return async function () {
        return await (
          new Promise(resolve => setTimeout(resolve, delay, value))
        );
      };
    }
    
    
    // the proposed helper which creates an async generator.
    async function* createDeferredValuesPool(asyncFunctions) {
      let asyncFct;
      while (asyncFct = asyncFunctions.shift()) {
    
        yield (await asyncFct());
      }
    }
    
    // helper task which creates a list of async functions.
    const deferredValueActions = [
      ["a", 2000],
      ["b", 3000],
      ["c", 1000],
    ].map(([value, delay]) =>
      createDeferredValueAction(value, delay)
    );
    
    // the OP's actual task(s)
    
    // create an async generator ...
    const deferredValuesPool =
      createDeferredValuesPool(deferredValueActions);
    
    (async () => {
      // ... and iterate over it.
      for await (const value of deferredValuesPool) {
        console.log({ value });
      }
    })();
    
    console.log('... running ...');
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    Edit

    Learning from the above deferred-values generator-example on could come back to the OP's original code which due to its simplicity still is handled best by just another then.

    But there might be cases where one has to chain an unknown number of async functions each relying on the result of the one called before. Thus one has to come up with a generic solution for thenables.

    A possible solution was to just refactor the async generator function createDeferredValuesPool into createThenablesPool where the changes are minor ...

    // the OP's original TS code
    // stripped and formatted into JS
    
    // const getUsersIds = () =>
    //   API.Users.getUsersIds().then(
    //     action(res => state.request.userIds = res)
    //   );
    // const getUsers = () =>
    //   API.Users.getUsers(state.request).then(
    //     action(res => state.users = res)
    //   );
    
    // rewrite both above getters into (thenable)
    // async functions which each returns its result.
    
    async function getUsersIds() {
      // return (await API.Users.getUsersIds());
    
      // fake it for demonstration purpose only.
      return (await new Promise(resolve =>
        setTimeout(resolve, 1500, [123, 456, 789])
      ));
    }
    async function getUsers(userIds) {
      // return (await API.Users.getUsers({ userIds }));
    
      // fake it for demonstration purpose only.
      return (await new Promise(resolve =>
        setTimeout(resolve, 1500, userIds
    
          // prove that the `userIds` result
          // from the before API call got
          // passed to the next/this one.
          .map(function (id, idx) {
            return {
              id,
              name: this[idx],
            };
          }, ['foo', 'bar', 'baz'])
        )
      ));
    }
    
    // the proposed helper which creates
    // an async generator of thenables.
    async function* createThenablesPool(asyncFunctions) {
      let result;
      let asyncFct;
      while (asyncFct = asyncFunctions.shift()) {
    
        result = await asyncFct(result);
        // debugger;
    
        yield result;
      }
    }
    
    // the OP's actual task(s)
    (async () => {
    
      for await (
        const value of createThenablesPool([getUsersIds, getUsers])
      ) {
        console.log({ value });
      }
    })();
    
    console.log('... running ...');
    .as-console-wrapper { min-height: 100%!important; top: 0; }