inexorabletash / indexeddb-promises

Proposal for incremental support for Promises in the Indexed DB API
48 stars 2 forks source link

Can you schedule more tasks after waiting promises resolve? #11

Open inexorabletash opened 8 years ago

inexorabletash commented 8 years ago

Let's dig up the old example of get-fetch-put:

tx.waitUntil((async () => {
  const url = await tx.objectStore('urls').get(k);
  const request = await fetch(url);
  const text = await request.text();
  await tx.objectStore('bodies').put(text, k);
})());

It is conceptually simpler to write as:

  const url = await tx.objectStore('urls').get(k);

  const req_p = fetch(url);
  tx.waitUntil(req_p);
  const request = await req_p;

  const text_p = request.text();
  tx.waitUntil(text_p);
  const text = await text_p;

  await tx.objectStore('bodies').put(text, k);
})());

(See commentary in #3 and #9 about simplifying the x_p/waitUntil(X_p)/await x_p pattern)

... but to do that we need to ensure that after the waiting promise fires we get another "tick" to schedule more work, otherwise the commit will start before control returns to script:

  const text_p = request.text();
  tx.waitUntil(text_p);
  const text = await text_p;

  // text_p will resolve before the next line of code executes, so commit could initiate

  await tx.objectStore('bodies').put(text, k);

One way to address this would be to specify that:

once all of the promises in transaction's extend lifetime promises fulfill:

  1. Let count be the number of promises in extend lifetime promises
  2. Queue a microtask to run the following substeps:
    1. If the transaction has aborted, abort these steps
    2. If the number of promises in extend lifetime promises is greater than count, abort these steps.
    3. If there are any requests in request list with done flag unset, abort these steps.
    4. Set transaction's state to "committing" and the transaction attempts to commit.

That is, don't try to commit immediately but queue a microtask to schedule a commit. That will ensure that microtasks queued on a waiting promise get to execute before we try to commit. But it's still subtle.

Maybe we need to queue a task rather than queue a microtask?

dfahlander commented 7 years ago

I reacted exactly like @inexorabletash about this. It's not clear in README what implications the "waiting" state has on that transactions, except by the code snippets, where it seems possible to use the transaction as if it was in the active state. I hope that is the case, and that the state remains "waiting" after a transaction has been operated on.

Assuming that waiting state is equivalent to active state when operating on the database, we could see that waitUntil() will be the thing you always begin with after creating a transaction, so that the rest of the code doesn't need to worry about transaction committing but can start rely on promises instead.

If I've understood this correctly, the pattern that everyone will likely use, is to always start a transaction using waitUntil() and then do everything in a promise-returning function:

function doSomethingInTransaction (db) {
    let trans = db.transaction(['items'], 'readwrite');
    return trans.waitUntil((async ()=>{
        // Do your code here
    })());
}