Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Microtasks inside Web Workers

The distinction between tasks and microtasks is important because IndexedDB transactions commit across tasks, but not microtasks. This is problematic when wrapping IndexedDB code in Promises, because in Firefox (and maybe other browsers), promise resolution does not happen in a microtask, so your transaction will commit.

The solution to this problem is to use a third-party promise implementation that uses microtasks. lie is one of those libraries, and under the hood it abstracts the microtask problem into another library called immediate, which uses MutationObserver to generate microtasks.

That works great, most of the time. But in a Web Worker, MutationObserver doesn't exist, so that trick won't work. Here's an example of the problem in an easily-runnable GitHub repo. Basically I have this code:

var immediate = require('immediate');

var openRequest = indexedDB.open('firefox-indexeddb-promise-worker-test');

openRequest.onupgradeneeded = function() {
    var db = openRequest.result;
    var store = db.createObjectStore('whatever', {keyPath: 'id'});

    store.put({id: 1});
    store.put({id: 2});
    store.put({id: 3});
};

function get(tx, id, cb) {
    immediate(function () {
        var req = tx.objectStore('whatever').get(id);
        req.onsuccess = function (e) {
            console.log('got', e.target.result);
            if (cb) {
                cb(null, e.target.result);
            }
        };
        req.onerror = function (e) {
            console.error(e.target.error);
            if (cb) {
                cb(e.target.error);
            }
        };
    });
}

openRequest.onsuccess = function() {
    var db = openRequest.result;

    var tx = db.transaction('whatever');
    tx.oncomplete = function () {
        console.log('tx complete');
    };

    get(tx, 1, function () {
        get(tx, 2);
    });
};

When I run that normally, it works fine. When I run it in a Web Worker, it fails because the transaction commits when immediate is called, before the callback runs. This happens in both Chrome and Firefox.

As of now, I've thought of two solutions:

  1. Don't use promises, go back to callback hell
  2. Use promises with synchronous resolution

Both of those options are highly unsatsifying. So I ask you, Stack Overflow, do you know of a way to queue microtasks inside of a Web Worker?

like image 907
dumbmatter Avatar asked Mar 07 '17 23:03

dumbmatter


1 Answers

short answer: you can't do that in a web worker

long answer: there is no actual microtask api, there are just hacks to try to simulate them. Unfortunately the ones that work best (mutation observer,) mainly have to do with the DOM so they are only available in the main thread not in a web worker. That being said it may make sense for IDB and promises to standardize an official relationship, I'm not sure if there actually is one speced out as promises and IDB are from different groups. There actually might be some traction from browser vendors about doing a real microtask api inside of web workers as most of the objections have to do with accidentally hosing the main thread.

like image 111
Calvin Avatar answered Sep 22 '22 07:09

Calvin