10

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?

Community
  • 1
  • 1
dumbmatter
  • 9,351
  • 7
  • 41
  • 80
  • Promises with synchronous execution of callbacks within the resolver aren't as bad as they sound. You'd only have to ensure that a) the resolution itself is triggered asynchronously (or at least at the end of the turn) and b) you can trust the party that triggers the resolution – Bergi Mar 08 '17 at 02:11
  • Opening sentence of question talks about "macrotasks", but thereafter "microtasks". Is this intentional and if so could you please explain? – Roamer-1888 Mar 08 '17 at 08:43
  • @Bergi are you aware of a library that behaves the way you describe? – dumbmatter Mar 08 '17 at 13:58
  • @Roamer-1888 it was a typo, thanks. I believe "task" and "macrotask" are synonymous in this context, and "microtask" is something different. – dumbmatter Mar 08 '17 at 13:58
  • @dumbmatter I [had written one myself](https://github.com/bergus/F-promise), but I guess you can use any that allows you to supply your own scheduler – Bergi Mar 08 '17 at 14:02
  • Thanks. I'll have to look a bit deeper I guess, but my initial result when trying that is errors because it uses `setImmediate` which is only supported in IE. – dumbmatter Mar 08 '17 at 15:45
  • @dumbmatter, yes, that is my understanding too. – Roamer-1888 Mar 08 '17 at 21:17
  • 2 quick things, setImmediate is a macrotask not a microtask and it's available in only node and IE. Promises have been speced out to be async for some good reasons, you almost certainly are going to have some pain points if you use a non standard sync promise library. – Calvin Mar 13 '17 at 12:36
  • You should be able to substitute `Proxy` for `MutationObserver` within `Worker` – guest271314 Mar 13 '17 at 23:23
  • I re-read your question several times, but I don't understand why you wrapped the interior of the get function in `immediate(function () {` at the first place. – Lorenz Meyer Mar 14 '17 at 06:02
  • @guest271314 - I don't think so, Proxy responses are synchronous, right? – dumbmatter Mar 14 '17 at 13:14
  • @LorenzMeyer - It's wrapped in `immediate` to illustrate the simplest case of bug I'm trying to work around. The real goal is to use promises here. Since native promises don't work in this scenario in some browsers (like Firefox), I need a polyfill. And a polyfill can't work unless I can get this simple example to work, because a polyfill needs some way to asynchronously execute a function without committing an IndexedDB transaction. – dumbmatter Mar 14 '17 at 13:16
  • @dumbmatter Note, have not tried `IndexedDB`. If interpret Question correctly you could try to define each property of `IndexedDB` as a `Proxy` object to get `get`, `set` function calls [How to detect when a JavaScript object variable is accessed?](http://stackoverflow.com/questions/41405080/how-to-detect-when-a-javascript-object-variable-is-accessed). Still not sure what issue is? – guest271314 Mar 14 '17 at 20:04

1 Answers1

5

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.

Calvin
  • 784
  • 4
  • 10
  • https://github.com/inexorabletash/indexeddb-promises/ the work towards standardizing IDB and promises, but there doesn't seem to be much traction there (I'm not a browser dev though, so hopefully I'm wrong about that). – dumbmatter Mar 14 '17 at 13:19