node/test/parallel/test-abortsignal-cloneable.js
James M Snell 29a909ab22 lib: make AbortSignal cloneable/transferable
Allows for using `AbortSignal` across worker threads and contexts.

```js
const ac = new AbortController();
const mc = new MessageChannel();
mc.port1.onmessage = ({ data }) => {
  data.addEventListener('abort', () => {
    console.log('aborted!');
  });
};
mc.port2.postMessage(ac.signal, [ac.signal]);
```

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/41050
Refs: https://github.com/whatwg/dom/issues/948
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
2021-12-10 19:22:05 -08:00

79 lines
2.2 KiB
JavaScript

'use strict';
const common = require('../common');
const { ok, strictEqual } = require('assert');
const { setImmediate: pause } = require('timers/promises');
function deferred() {
let res;
const promise = new Promise((resolve) => res = resolve);
return { res, promise };
}
(async () => {
const ac = new AbortController();
const mc = new MessageChannel();
const deferred1 = deferred();
const deferred2 = deferred();
const resolvers = [deferred1, deferred2];
mc.port1.onmessage = common.mustCall(({ data }) => {
data.addEventListener('abort', common.mustCall(() => {
strictEqual(data.reason, 'boom');
}));
resolvers.shift().res();
}, 2);
mc.port2.postMessage(ac.signal, [ac.signal]);
// Can be cloned/transferd multiple times and they all still work
mc.port2.postMessage(ac.signal, [ac.signal]);
mc.port2.close();
// Although we're using transfer semantics, the local AbortSignal
// is still usable locally.
ac.signal.addEventListener('abort', common.mustCall(() => {
strictEqual(ac.signal.reason, 'boom');
}));
await Promise.all([ deferred1.promise, deferred2.promise ]);
ac.abort('boom');
// Because the postMessage used by the underlying AbortSignal
// takes at least one turn of the event loop to be processed,
// and because it is unref'd, it won't, by itself, keep the
// event loop open long enough for the test to complete, so
// we schedule two back to back turns of the event to ensure
// the loop runs long enough for the test to complete.
await pause();
await pause();
})().then(common.mustCall());
{
const signal = AbortSignal.abort('boom');
ok(signal.aborted);
strictEqual(signal.reason, 'boom');
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => {
ok(data instanceof AbortSignal);
ok(data.aborted);
strictEqual(data.reason, 'boom');
mc.port1.close();
});
mc.port2.postMessage(signal, [signal]);
}
{
// The cloned AbortSignal does not keep the event loop open
// waiting for the abort to be triggered.
const ac = new AbortController();
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall();
mc.port2.postMessage(ac.signal, [ac.signal]);
mc.port2.close();
}