2

I am trying to offload long running process that blocks my UI.

The WebWorker approach appears to be the best approach to this situation.

However, one of the libraries that I need to use has async/await.

The WebWorker has a limited JS API and does not appear to have async/await.

Is there a way to use Promises inside of a WebWorker?

Error

ReferenceError: __awaiter is not defined

Regards,

Daniel

Update:

Adding an __awaiter lead to the Promise not being recognised.

var __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, Promise, generator) { return new Promise(function(resolve, reject) { generator = generator.call(thisArg, _arguments); function cast(value) { return value instanceof Promise && value.constructor === Promise ? value : new Promise(function(resolve) { resolve(value); }); } function onfulfill(value) { try { step('next', value); } catch (e) { reject(e); } } function onreject(value) { try { step('throw', value); } catch (e) { reject(e); } } function step(verb, value) { var result = generator[verb](value); result.done ? resolve(result.value) : cast(result.value).then(onfulfill, onreject); } step('next', void 0); }); } 

Here is the skeleton code for how the web worker is constructed.

/* eslint-disable */ export default function ModelWorker() { this.window = this onmessage = async (e) => { console.log(e); } } 

WorkerProxy.js

export default class WorkerProxy { constructor(worker) { const code = worker.toString() const src = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}')) const blob = new Blob([src], { type: 'application/javascript' }) return new Worker(URL.createObjectURL(blob)) } } 

SomeComponent.js

import WorkerProxy from './WorkerProxy' import ModelWorker from './ModelWorker' if (!!window.Worker) { const worker = new WorkerProxy(ModelWorker) worker.addEventListener('message', e => console.log(e.data)) worker.postMessage({ message:message }) // Load labels, etc. } 
4

2 Answers 2

8

async/await are part of ECMAScript language, and these are available in all global scopes, be it Window, Web Worker, Service Worker, Audio Worklet, Paint Worklet etc.

What these scopes may not have are some Web APIs, like the DOM API, MediaDevices API, Geolocation API and a few others. However, as long as the browser supports this ECMAScript feature, then all scopes will.

So I can't tell what is your problem, but it is definitely possible to use Promises and async/await in a Worker.

const worker = new Worker( URL.createObjectURL( new Blob([worker_script.textContent]) ) ); worker.onmessage = ({data}) => console.log(data);
<script type="worker-script" id="worker_script"> (async function() { postMessage(['starting', performance.now()]); await wait(2000); postMessage(['ended', performance.now()]); })(); function wait(time) { return new Promise((res)=>setTimeout(res, time)); } </script>

Sign up to request clarification or add additional context in comments.

Comments

0

You absolutely can use async-await with WebWorkers and in WebWorkers.
And if some prehistoric browser doesn't support the async-await keywords,
you can transpile them down to ES5 with promises.
This even worked for IE11.

Here's the tsconfig to transpile promises to ES5:

{ "compileOnSave": true, "compilerOptions": { "target": "ES5", //"target": "es5", //"module": "amd", // define, export, like playground // "module": "commonjs", // require "module": "ES2015", // just like typescript // "module": "system", // System.register // "module": "umd", // factory, require, exports // "module": "esnext", // like typescript // "module": "none", // Object.defineProperty, require // "module": "commonjs", "lib": [ "DOM", "ES2016", "ES2015.Promise", "ES2015.Proxy" ], "noImplicitAny": true, "noEmitOnError": true, "removeComments": true, "sourceMap": false, "outDir": "../../wwwroot/pdfstamps", "rootDir": "./", "allowUnreachableCode": true //,"alwaysStrict ": true, }, "exclude": [ "node_modules", "wwwroot" ], "include": [ "**/*" ] } 

Here's a sample, on how to do it.
It calls a worker function with await, and expects the result. In processData, the worker creates random delays between 1 and 5 seconds, and then returns the data you passed. 50% of the time ( as in getRandomInt(1,2) ), it will return an exception, so you see this handles exceptions as well.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> <meta http-equiv="Pragma" content="no-cache"> <title>Whitespace Removal Benchmark</title> <style> .loader { width: 30px; aspect-ratio: 4; --_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000); background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%; background-size: calc(100%/3) 100%; animation: l7 1s infinite linear; } @keyframes l7 { 33% { background-size: calc(100%/3) 0%,calc(100%/3) 100%,calc(100%/3) 100% } 50% { background-size: calc(100%/3) 100%,calc(100%/3) 0%,calc(100%/3) 100% } 66% { background-size: calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0% } } </style> </head> <body> <h1>Whitespace Removal Benchmark</h1> <div id="result"> <div class="loader" style="display: inline-block;"></div> <div style="display: inline-block; font-size: 5mm;" class="loading">Benchmark running&nbsp;</div> <div class="loader" style="display: inline-block;"></div> </div> <!-- https://css-loaders.com/dots/ --> <script> const workerCode = `"use strict"; function cryptoRand() { const randomBuffer = new Uint32Array(1); (crypto || msCrypto).getRandomValues(randomBuffer); return ( randomBuffer[0] / (0xffffffff + 1) ); } function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(cryptoRand() * (max - min + 1)) + min; } function sleep(interval) { return new Promise( function (resolve, reject) { let wait = setTimeout( function () { clearTimeout(wait); if(getRandomInt(1,2) % 2 == 0) resolve(); else reject(new Error("Promise timed out ! ")); } , interval ); }); } async function processData(data) { let interval = getRandomInt(1000, 5000); await sleep(interval); return data; } async function handleSomeTask(id, data) { try { // Process the data const result = await processData(data); // throw new Error("hello"); self.postMessage({ "id": id, "err":null, "result": result }); } catch (err) { self.postMessage({ "id": id, "err":err, "result": null }); } } self.onmessage = function(event) { const { id, data } = event.data; handleSomeTask(id, data); }; `; const blob = new Blob([workerCode], { type: 'text/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.onmessage = function(event) { const { id, err, result } = event.data; // console.log("received", event.data); if (err != null) { const reject = worker.workerErrorCallbacks.get(id); if (reject) { reject(err); worker.workerSuccessCallbacks.delete(id); worker.workerErrorCallbacks.delete(id); } } else { const resolve = worker.workerSuccessCallbacks.get(id); if (resolve) { resolve(result); worker.workerSuccessCallbacks.delete(id); worker.workerErrorCallbacks.delete(id); } } }; function generateRandom32BitInteger() { var array = new Int8Array(4); (window.crypto || window.msCrypto).getRandomValues(array); var dataView = new DataView(array.buffer); var uint = dataView.getUint32(); // var f = uint / (0xffffffff + 1); // 0xFFFFFFFF = uint32.MaxValue (+1 because Math.random is inclusive of 0, but not 1) // return f; return uint; } function generateRandom128BitInteger() { const array = new Uint8Array(16); crypto.getRandomValues(array); let value = 0n; for (let i = 0; i < array.length; i++) { value = (value << 8n) + BigInt(array[i]); } return value; } function generateRandomUuid() { const array = new Uint8Array(16); crypto.getRandomValues(array); let value = 0n; for (let i = 0; i < array.length; i++) { value = (value << 8n) + BigInt(array[i]); } // return value; const hexString = value.toString(16).padStart(32, '0'); // Format the hex string into UUID format const uuid = `${hexString.slice(0, 8)}-${hexString.slice(8, 12)}-${hexString.slice(12, 16)}-${hexString.slice(16, 20)}-${hexString.slice(20)}`; return uuid; } async function callWorkerFunction(worker, data) { if (!worker.workerSuccessCallbacks) { worker.workerSuccessCallbacks = new Map(); worker.workerErrorCallbacks = new Map(); // worker.callbackId = 0;; } // const id = Math.random(); // Generate a unique identifier // const id = generateRandomUuid(); // const id = generateRandom128BitInteger(); const id = generateRandom32BitInteger(); // const id = worker.callbackId++; return new Promise( function (resolve, reject) { // worker.onmessage = function(event) // { // console.log("received event", event.data); // if (event.data.id === id) // { // resolve(event.data.result); // // worker.terminate(); // Optional: Terminate the worker after the result // } // }; worker.workerSuccessCallbacks.set(id, resolve); worker.workerErrorCallbacks.set(id, reject); worker.postMessage({ id, data }); } ); } function generateRandomString(min, max) { const length = Math.floor(Math.random() * (max - min + 1)) + min; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; let result = []; for (let i = 0; i < length; i++) { result.push(characters.charAt(Math.floor(Math.random() * charactersLength))); } return result.join(""); } async function testCall() { try { const randomString = generateRandomString(5, 15); let passedData = { "hello": "kitty", "foo": "bar", "something": randomString }; const result = await callWorkerFunction(worker, passedData); console.log("result for " + randomString + ":", result); } catch (err) { console.log("failed:", err); } } function test() { for (let i = 0; i < 20; ++i) { testCall(); } } test(); </script> </body> </html> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.