Search code examples
javascriptecmascript-6async-awaites6-promiseecmascript-2017

ECMAScript 2017 concurrent async functions with possibility to wait for subpromises that should not be concurrent?


Suppose we have multiple concurrent tasks that each does at least one web request at one point or another and they take a while to complete. Suppose they also may or may not at some arbitrary point in execution run a function that returns a promise. Suppose there is also a need to make sure some of these promises never run at the same time.

I could make a single meeting point where all these concurrent tasks would meet, create a promise chain out of those functions that return promises that should not run at the same time, and continue after all this is done. This has a downside of potential useless waiting if some of these tasks arrive at the meeting point significantly sooner than others but still have a lot of work to do after the meeting point.

Also, I have tried implementing a simple promise queue like so:

function PromiseQueue() {
    var promise = Promise.resolve();

    return {
        push: function(fn) {
            promise = promise.then(fn, fn);
            return this;
        }
    }
}

This queue works but has a problem that I don't see a way to send a function to the queue from one of the concurrent tasks and also wait for results that will be available only once the queue decides to process the sent item, not to mention that the promise is not yet constructed at the time function is sent to the queue so there is nothing to wait for at the time the promise spawning function is sent to the queue.

I would like to achieve the above mentioned with sticking to ECMAScript 2017 async/await format and ES6 promises as much as possible.

Below is an example of the described issue with 2 concurrent tasks, where subPromise1 and subPromise2 should not be executed at the same time but as currently written have the potential to, but I would like a general solution for an arbitrary number of concurrent tasks.

async function async1() {
    //do some long running work
    const sPResult = await subPromise1;
    //do some more long running work depending on sPResult
}

async function async2() {
    //do some different long running work
    const sPResult = await subPromise2;
    //do some more different long running work depending on sPResult
}

async function mainFunction() {
    //something
    const totalResult = await Promise.all([async1,async2]);
    //something else
}

EDIT: Here is a working fiddle where issue as originally described can be seen: https://jsfiddle.net/40gchrw6/

EDIT: Here is a working solution: https://jsfiddle.net/f2ewsugm/1/


Solution

  • Create an enclosing scope (a class might make sense) for the chain of promises that must be performed in sequence. When you reach a step that must be sequential, add it to the chain.

    Class AsyncThing {
      constructor() {
        this.promises = Promise.resolve()
      }
    
      async async1() {
        //do some long running work
        // assuming subPromise1 is a promise-returning function that must be chained
        let sPResult
        this.promises = this.promises.then(subPromise1).then(result => sPResult = result)
        await this.promises;
        //do some more long running work depending on sPResult
      }
    
      async async2() {
        //do some different long running work
        // assuming subPromise2 is a promise-returning function that must be chained
        let sPResult
        this.promises = this.promises.then(subPromise2).then(result => sPResult = result)
        await this.promises;
        //do some more different long running work depending on sPResult
      }
    
      async mainFunction() {
        //something
        const totalResult = await Promise.all([async1,async2]);
        //something else
      }
    }