-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Sindre Sorhus <[email protected]>
- Loading branch information
1 parent
de08673
commit f73a5ab
Showing
6 changed files
with
206 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,129 @@ | ||
import Worker from 'web-worker'; | ||
import {pEvent} from 'p-event'; | ||
|
||
const isNode = Boolean(globalThis.process?.versions?.node); | ||
|
||
const makeBlob = content => new globalThis.Blob([content], {type: 'text/javascript'}); | ||
|
||
// TODO: Remove this when targeting Node.js 18 (`Blob` global is supported) and if https://github.com/developit/web-worker/issues/30 is fixed. | ||
const makeDataUrl = content => { | ||
const data = globalThis.Buffer.from(content).toString('base64'); | ||
return `data:text/javascript;base64,${data}`; | ||
}; | ||
|
||
function createWorker(content) { | ||
let url; | ||
let worker; | ||
|
||
const cleanup = () => { | ||
if (url) { | ||
URL.revokeObjectURL(url); | ||
} | ||
|
||
worker?.terminate(); | ||
}; | ||
|
||
if (isNode) { | ||
worker = new Worker(makeDataUrl(content), {type: 'module'}); | ||
} else { | ||
url = URL.createObjectURL(makeBlob(content)); | ||
worker = new Worker(url, {type: 'module'}); | ||
} | ||
|
||
return { | ||
worker, | ||
cleanup, | ||
}; | ||
} | ||
|
||
const makeContent = function_ => | ||
` | ||
globalThis.onmessage = async event => { | ||
globalThis.onmessage = async ({data: arguments_}) => { | ||
try { | ||
const output = await (${function_.toString()})(...event.data); | ||
const output = await (${function_.toString()})(...arguments_); | ||
globalThis.postMessage({output}); | ||
} catch (error) { | ||
globalThis.postMessage({error}); | ||
} | ||
}; | ||
`; | ||
|
||
const makeBlob = function_ => new globalThis.Blob([makeContent(function_)], {type: 'text/javascript'}); | ||
export default function makeAsynchronous(function_) { | ||
return async (...arguments_) => { | ||
const {worker, cleanup} = createWorker(makeContent(function_)); | ||
|
||
// TODO: Remove this when targeting Node.js 18 (`Blob` global is supported) and if https://github.com/developit/web-worker/issues/30 is fixed. | ||
const makeDataUrl = function_ => { | ||
const data = globalThis.Buffer.from(makeContent(function_)).toString('base64'); | ||
return `data:text/javascript;base64,${data}`; | ||
}; | ||
try { | ||
const promise = pEvent(worker, 'message', { | ||
rejectionEvents: ['error', 'messageerror'], | ||
}); | ||
|
||
export default function makeAsynchronous(function_) { | ||
return (...arguments_) => new Promise((resolve, reject) => { | ||
let url; | ||
let worker; | ||
worker.postMessage(arguments_); | ||
|
||
const cleanup = () => { | ||
if (url) { | ||
URL.revokeObjectURL(url); | ||
} | ||
const {data: {output, error}} = await promise; | ||
|
||
worker?.terminate(); | ||
}; | ||
if (error) { | ||
throw error; | ||
} | ||
|
||
const failure = error => { | ||
return output; | ||
} finally { | ||
cleanup(); | ||
reject(error); | ||
}; | ||
} | ||
}; | ||
} | ||
|
||
const makeIterableContent = function_ => | ||
` | ||
const nothing = Symbol('nothing'); | ||
let iterator = nothing; | ||
globalThis.onmessage = async ({data: arguments_}) => { | ||
try { | ||
if (isNode) { | ||
worker = new Worker(makeDataUrl(function_), {type: 'module'}); | ||
} else { | ||
url = URL.createObjectURL(makeBlob(function_)); | ||
worker = new Worker(url, {type: 'module'}); | ||
if (iterator === nothing) { | ||
iterator = await (${function_.toString()})(...arguments_); | ||
} | ||
const output = await iterator.next(); | ||
globalThis.postMessage({output}); | ||
} catch (error) { | ||
failure(error); | ||
return; | ||
globalThis.postMessage({error}); | ||
} | ||
}; | ||
`; | ||
|
||
worker.addEventListener('message', ({data}) => { | ||
if (data.error) { | ||
failure(data.error); | ||
} else { | ||
cleanup(); | ||
resolve(data.output); | ||
} | ||
}); | ||
export function makeAsynchronousIterable(function_) { | ||
return (...arguments_) => ({ | ||
async * [Symbol.asyncIterator]() { | ||
const {worker, cleanup} = createWorker(makeIterableContent(function_)); | ||
|
||
worker.addEventListener('messageerror', error => { | ||
failure(error); | ||
}); | ||
try { | ||
let isFirstMessage = true; | ||
|
||
worker.addEventListener('error', error => { | ||
failure(error); | ||
}); | ||
while (true) { | ||
const promise = pEvent(worker, 'message', { | ||
rejectionEvents: ['messageerror', 'error'], | ||
}); | ||
|
||
try { | ||
worker.postMessage(arguments_); | ||
} catch (error) { | ||
failure(error); | ||
} | ||
worker.postMessage(isFirstMessage ? arguments_ : undefined); | ||
isFirstMessage = false; | ||
|
||
const {data: {output, error}} = await promise; // eslint-disable-line no-await-in-loop | ||
|
||
if (error) { | ||
throw error; | ||
} | ||
|
||
const {value, done} = output; | ||
|
||
if (done) { | ||
break; | ||
} | ||
|
||
yield value; | ||
} | ||
} finally { | ||
cleanup(); | ||
} | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,14 @@ | ||
import {expectType} from 'tsd'; | ||
import makeAsynchronous from './index.js'; | ||
import makeAsynchronous, {makeAsynchronousIterable} from './index.js'; | ||
|
||
const fn = makeAsynchronous((number: number) => number * 2); // eslint-disable-line @typescript-eslint/no-unsafe-assignment | ||
|
||
expectType<Promise<number>>(fn(2)); | ||
|
||
const fn2 = makeAsynchronousIterable(function * () { // eslint-disable-line @typescript-eslint/no-unsafe-assignment | ||
for (let number = 1; ; number++) { | ||
yield number; | ||
} | ||
}); | ||
|
||
expectType<AsyncIterable<number>>(fn2()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,7 @@ | |
"pool" | ||
], | ||
"dependencies": { | ||
"p-event": "^5.0.1", | ||
"type-fest": "^2.14.0", | ||
"web-worker": "^1.2.0" | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters