@NicoloRibaudo
Synchronously call your
async functions
NICOLÒ RIBAUDO
@nicolo-ribaudo
@NicoloRibaudo hello@nicr.dev
@NicoloRibaudo 2
NICOLÒ RIBAUDO
● Working at Igalia on web standards
● Maintaining Babel, the JavaScript compiler
nribaudo@igalia.com
@NicoloRibaudo
@nicolo-ribaudo
@nic@tech.lgbt
@nicolo-ribaudo:matrix.org
@NicoloRibaudo
Why would you want to
do it???
3
@NicoloRibaudo
This is bad, don't do it!
4
* unless you need to do it
*
@NicoloRibaudo
Why would you want to do it???
● 2021–2023 — We migrated Babel from internally using
CommonJS to using ESM.
5
🥳
@NicoloRibaudo
Why would you want to do it???
● Babel supports loading external plugins (actually,
everything is a plugin)
6
// babel.config.json
{
"plugins": [
"@babel/plugin-proposal-decorators"
]
}
// Somewhere inside Babel (previously)
function loadAllPlugins() {
const plugin = require(pluginName);
}
// Somewhere inside Babel (now)
async function loadAllPlugins() {
const plugin = await import(pluginName);
}
@NicoloRibaudo
Why would you want to do it???
● async/await virally makes everything asynchronous
7
// Someone using Babel (previously)
import * as babel from "@babel/core";
const code = babel.transform(input, {
// ... config
});
// Someone using Babel (now)
import * as babel from "@babel/core";
const code = await babel.transform(input, {
// ... config
});
@NicoloRibaudo
Why would you want to do it???
● Sometimes you can force😇 asynchronous APIs on
your users — sometimes you can't.
8
@babel/eslint-parser is an ESLint parser to support experimental syntax;
@babel/register hooks into Node.js' require() to compile files on-the-fly.
@NicoloRibaudo
Worker and Atomics
9
@NicoloRibaudo
Worker and Atomics
10
@NicoloRibaudo
Worker and Atomics
11
JavaScript is a multi-threaded programming language:
- new Worker() to spawn multiple threads
- new SharedArrayBuffer() to share memory across
threads
- Atomics.* for thread-safe operations on shared
memory
Atomics.add, Atomics.xor, Atomics.store, Atomics.load,
Atomics.wait, Atomics.notify, Atomics.isLockFree, ...
@NicoloRibaudo
Worker and Atomics
Main thread
function doSomethingSync() {
● Setup a synchronization channel
● Wait until the worker is done
● Read the received result
}
12
Worker thread
addListener("message", () => {
● Asynchronously computer the
result
● Notify the main thread that the
result is ready
});
Send the result back to
the main thread
Share some data and the
synchronization channel with the
worker
@NicoloRibaudo
Worker and Atomics
Main thread
function doSomethingSync() {
const signal = new Int32Array(new SharedArrayBuffer(4));
const { port1, port2 } = new MessageChannel();
// sleep
Atomics.wait(signal, 0, 0);
const { result } = receiveMessageOnPort(port2);
return result;
}
13
Worker thread
addListener("message", () => {
let result =
await doSomethingAsync();
port1.postMessage({ result });
Atomics.notify(signal, 0);
});
signal, port1
{ payload: /* ... */ }
{ result: /* ... */ }
@NicoloRibaudo
Worker and Atomics
Main thread
1. Create the worker
const { Worker, SHARE_ENV } = require("worker_threads");
const worker = new Worker("./path/to/worker.js", {
env: SHARE_ENV,
});
14
@NicoloRibaudo
Worker and Atomics
Main thread, doSomethingSync
2. Delegate the task to the worker
const { MessageChannel } = require("worker_threads");
const signal = new Int32Array(new SharedArrayBuffer(4));
const { port1, port2 } = new MessageChannel();
worker.postMessage({ signal, port: port1, payload }, [port1]);
Atomics.wait(signal, 0, 0);
15
@NicoloRibaudo
Worker and Atomics
Worker thread, "message" listener
3. Perform the task and send the result to the main thread
const result = await doSomethingAsync();
port.postMessage({ result });
port.close();
Atomics.notify(signal, 0);
16
@NicoloRibaudo
Worker and Atomics
Main thread, doSomethingSync
4. After waking up, read the result and return it
const { receiveMessageOnPort } = require("worker_threads");
... const { port1, port2 } = new MessageChannel(); ...
... Atomics.wait(signal, 0, 0); ...
const { result } = receiveMessageOnPort(port2);
return result;
17
@NicoloRibaudo
Production code? Error handling
18
In case of an error, we
must manually report it
to the main thread.
@NicoloRibaudo
Usage outside of
Node.js
19
@NicoloRibaudo
Usage outside of Node.js
20
● In web browsers, the main thread cannot sleep. You can only
synchronously call your asynchronous functions from other web workers.
● receiveMessageOnPort is only available in Node.js. When using
browsers or browser-compatible engines, you need to manually serialize
your data on a SharedArrayBuffer, and synchronously read and
deserialize them on the other side when it wakes up.
@NicoloRibaudo
Who's doing this?
21
@NicoloRibaudo
Who's doing this?
22
● Babel, in @babel/register and @babel/eslint-plugin
● Prettier, in @prettier/sync
● Node.js 20+ itself, to support synchronous import.meta.resolve while
moving ESM loader hooks to a separate thread (nodejs/node#44710)
@NicoloRibaudo
Thank you!
23
And remember to financially support
the open source libraries you are using
😘
Luca Casonato wants me to
explicitly say "Babel" here
@NicoloRibaudo
Worker and Atomics
24
More details:
https://giuseppegurgone.com/synchronizing-async-functions

Synchronously call your async functions

  • 1.
    @NicoloRibaudo Synchronously call your asyncfunctions NICOLÒ RIBAUDO @nicolo-ribaudo @NicoloRibaudo hello@nicr.dev
  • 2.
    @NicoloRibaudo 2 NICOLÒ RIBAUDO ●Working at Igalia on web standards ● Maintaining Babel, the JavaScript compiler nribaudo@igalia.com @NicoloRibaudo @nicolo-ribaudo @nic@tech.lgbt @nicolo-ribaudo:matrix.org
  • 3.
    @NicoloRibaudo Why would youwant to do it??? 3
  • 4.
    @NicoloRibaudo This is bad,don't do it! 4 * unless you need to do it *
  • 5.
    @NicoloRibaudo Why would youwant to do it??? ● 2021–2023 — We migrated Babel from internally using CommonJS to using ESM. 5 🥳
  • 6.
    @NicoloRibaudo Why would youwant to do it??? ● Babel supports loading external plugins (actually, everything is a plugin) 6 // babel.config.json { "plugins": [ "@babel/plugin-proposal-decorators" ] } // Somewhere inside Babel (previously) function loadAllPlugins() { const plugin = require(pluginName); } // Somewhere inside Babel (now) async function loadAllPlugins() { const plugin = await import(pluginName); }
  • 7.
    @NicoloRibaudo Why would youwant to do it??? ● async/await virally makes everything asynchronous 7 // Someone using Babel (previously) import * as babel from "@babel/core"; const code = babel.transform(input, { // ... config }); // Someone using Babel (now) import * as babel from "@babel/core"; const code = await babel.transform(input, { // ... config });
  • 8.
    @NicoloRibaudo Why would youwant to do it??? ● Sometimes you can force😇 asynchronous APIs on your users — sometimes you can't. 8 @babel/eslint-parser is an ESLint parser to support experimental syntax; @babel/register hooks into Node.js' require() to compile files on-the-fly.
  • 9.
  • 10.
  • 11.
    @NicoloRibaudo Worker and Atomics 11 JavaScriptis a multi-threaded programming language: - new Worker() to spawn multiple threads - new SharedArrayBuffer() to share memory across threads - Atomics.* for thread-safe operations on shared memory Atomics.add, Atomics.xor, Atomics.store, Atomics.load, Atomics.wait, Atomics.notify, Atomics.isLockFree, ...
  • 12.
    @NicoloRibaudo Worker and Atomics Mainthread function doSomethingSync() { ● Setup a synchronization channel ● Wait until the worker is done ● Read the received result } 12 Worker thread addListener("message", () => { ● Asynchronously computer the result ● Notify the main thread that the result is ready }); Send the result back to the main thread Share some data and the synchronization channel with the worker
  • 13.
    @NicoloRibaudo Worker and Atomics Mainthread function doSomethingSync() { const signal = new Int32Array(new SharedArrayBuffer(4)); const { port1, port2 } = new MessageChannel(); // sleep Atomics.wait(signal, 0, 0); const { result } = receiveMessageOnPort(port2); return result; } 13 Worker thread addListener("message", () => { let result = await doSomethingAsync(); port1.postMessage({ result }); Atomics.notify(signal, 0); }); signal, port1 { payload: /* ... */ } { result: /* ... */ }
  • 14.
    @NicoloRibaudo Worker and Atomics Mainthread 1. Create the worker const { Worker, SHARE_ENV } = require("worker_threads"); const worker = new Worker("./path/to/worker.js", { env: SHARE_ENV, }); 14
  • 15.
    @NicoloRibaudo Worker and Atomics Mainthread, doSomethingSync 2. Delegate the task to the worker const { MessageChannel } = require("worker_threads"); const signal = new Int32Array(new SharedArrayBuffer(4)); const { port1, port2 } = new MessageChannel(); worker.postMessage({ signal, port: port1, payload }, [port1]); Atomics.wait(signal, 0, 0); 15
  • 16.
    @NicoloRibaudo Worker and Atomics Workerthread, "message" listener 3. Perform the task and send the result to the main thread const result = await doSomethingAsync(); port.postMessage({ result }); port.close(); Atomics.notify(signal, 0); 16
  • 17.
    @NicoloRibaudo Worker and Atomics Mainthread, doSomethingSync 4. After waking up, read the result and return it const { receiveMessageOnPort } = require("worker_threads"); ... const { port1, port2 } = new MessageChannel(); ... ... Atomics.wait(signal, 0, 0); ... const { result } = receiveMessageOnPort(port2); return result; 17
  • 18.
    @NicoloRibaudo Production code? Errorhandling 18 In case of an error, we must manually report it to the main thread.
  • 19.
  • 20.
    @NicoloRibaudo Usage outside ofNode.js 20 ● In web browsers, the main thread cannot sleep. You can only synchronously call your asynchronous functions from other web workers. ● receiveMessageOnPort is only available in Node.js. When using browsers or browser-compatible engines, you need to manually serialize your data on a SharedArrayBuffer, and synchronously read and deserialize them on the other side when it wakes up.
  • 21.
  • 22.
    @NicoloRibaudo Who's doing this? 22 ●Babel, in @babel/register and @babel/eslint-plugin ● Prettier, in @prettier/sync ● Node.js 20+ itself, to support synchronous import.meta.resolve while moving ESM loader hooks to a separate thread (nodejs/node#44710)
  • 23.
    @NicoloRibaudo Thank you! 23 And rememberto financially support the open source libraries you are using 😘 Luca Casonato wants me to explicitly say "Babel" here
  • 24.
    @NicoloRibaudo Worker and Atomics 24 Moredetails: https://giuseppegurgone.com/synchronizing-async-functions