For more tips like this, sign up to the weekly newsletter!

Async synchronize

If you have a Java background, you might already be familiar with the synchronized keyword. It makes sure a function can only be executed by one of the threads at any single time.

Javascript is single-threaded, so this kind of synchronization is a non-issue here. But the same concept can be applied to asynchronous blocks. That is, when you want to make sure the execution of an async block can only start when the previous one is finished.

Why would you need it?

You might need to send a request to a target that does not support concurrent connections. That might happen when a web worker maintains internal state.

Or you have an error case that affects concurrent requests and you don't want to implement complex logic to handle it. As an example, a token that you need to refresh from time to time.

The solution

First, you need a function that maintains its internal state. This can be achieved with a closure:

const synchronize = (() => {
  let chain = ...;
  ...
  return () => {}
 })();

This creates a function that has access (and can also update) the chain variable.

Then you need to maintain the chain of promises and run them sequentially:

const synchronize = (() => {
  let chain = Promise.resolve();
  
  return async (promise) => {
    return chain = chain.then(promise);
  }
})()

Now when you call this functions, subsequent invocations will wait for an earlier to finish:

synchronize(async () => {
  log("Block 1 start");
  await wait(500);
  log("Block 1 end");
})

synchronize(async () => {
  log("Block 2 start");
  await wait(500);
  log("Block 2 end");
})

Prints:

Block 1 start
Block 1 end
Block 2 start
Block 2 end

The result value is also handled correctly:

const result = await synchronize(async () => {
  return 6
})
log(result) // 6
Try it
Learn more: