-1

Yes, I know, the question title is a bit provocatory. But let me explain.

I needed to execute a sequence of async tasks in JavaScript. They are async because I need them to be non-blocking, but I still want the current task to end before starting the next one. The order of tasks doesn't matter, the only important thing is that they are in mutual exclusion with each other.

For this reason, maybe because of C/C++ background, I was about to implement a queue and a system of locks. Then I stop to think if the problem could be solved just using async/await, and I ended up with this solution:

previousPromise = null;

async function enqueue(task) {
  while (previousPromise) {
    await previousPromise;
  }
  
  previousPromise = executeTask(task);
  await previousPromise;
  previousPromise = null;
}

In practice, I use the promise subscribers internal queue as the lock queue, in order to achieve a non-blocking wait. When the promise resolves, it awakes all the async tasks† and the first finding previousPromise equals to null will continue.

Here is a codepen example.

Is this solution solid? Thank you

  • Side note 1: a real lock system is obviously not needed. JavaScript is single-threaded, so there isn't any risk to be preempted after the while and before executeTask assignment.

  • Side note 2: the tasks arrive from the server in an asynchronous fashion, so I can't collect them all before executing.

† It actually calls the first callback in the subscriber's list

  • 2
    Your example locked up on me and activated the fans in my laptop after clicking it a few times, so I don't think it does what you think it does. – Blake Sep 3 '20 at 12:28
  • 2
    "but I still want the current task to end before starting the next one" Why? Can you explain your thinking so that we can know whether this is a good idea or not? – curiousdannii Sep 3 '20 at 12:47
  • 1
    Well you don't need locks in JavaScript because it's single-threaded - you only need the queue. – user253751 Sep 3 '20 at 19:52
  • 1
    your example code has a weird recursive anon function – Ewan Sep 4 '20 at 15:31
  • and your while loop loops forever – Ewan Sep 4 '20 at 15:34
0

OK so your code doesn't work because

while (previousPromise)

Is always true after the second click.

In any case its an interesting problem. Here is what i knocked up (below). Although I feel this is vulnerable to crashes stopping queue processing.

The main problem you have is having a Promise which hasn't started. If it's static code you can just use chain then(t=>{});

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script>
        //the 'queue'
        var tasks = [];

        //enquene a task and start it if we are not running
        async function enqueue(task) {
            var l = tasks.push(task);
            console.log("queue length: " + l);
            if (l == 1) {
                runNext();
            }
        }

        //recursively run tasks from the queue untill there are none left
        async function runNext() {
            if (tasks.length == 0) {
                return;
            }
            var t = tasks[0]; //get the first task
            await t(); //run the first task and await it
            tasks.shift() //remove the first task and throw it away
            runNext(); //run the next task
        }

        //our application
        var count = 1;
        function add() {
            var c = count++;
            console.log("adding " + c)
            var w = new Work()
            w.c = c;
            enqueue(w.execute.bind(w));
        }

        //need a class so we can hold the count without starting the function
        class Work
        {
            c;
            async execute() //this is the function we are going to queue up
            {
                console.log("executing: " + this.c);
                //add in a delay for demo purposes
                var r = Math.random();
                await new Promise(resolve => setTimeout(resolve, 10000 * r));
                console.log("finished: " + this.c);
            }
        }
        
    </script>
</head>
<body>
    <button onclick="add()"> Click me </button>
</body>
</html>

Also you can do this super sketchy version

    var activePromise;
    async function enqueue2(task) {
        if (activePromise == null)
        {
            activePromise = task();
        }
        else
        {
            activePromise = activePromise.then(async t => { await task(); })
        }
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.