forked from mystiq/hydrogen-web
detect when sync promise hack is needed
This commit is contained in:
parent
706ec97296
commit
98a6d82820
2 changed files with 44 additions and 8 deletions
|
@ -27,6 +27,7 @@ import "mdn-polyfills/Element.prototype.closest";
|
|||
// weighing a good extra 500kb :-(
|
||||
import "text-encoding";
|
||||
import Promise from "../lib/es6-promise/lib/es6-promise/promise.js";
|
||||
import {checkNeedsSyncPromise} from "./matrix/storage/idb/utils.js";
|
||||
|
||||
const flush = Promise._flush;
|
||||
Promise._flush = function() {
|
||||
|
@ -35,6 +36,8 @@ Promise._flush = function() {
|
|||
}
|
||||
|
||||
window.Promise = Promise;
|
||||
// TODO: should be awaited before opening any session in the picker
|
||||
checkNeedsSyncPromise();
|
||||
|
||||
// TODO: contribute this to mdn-polyfills
|
||||
if (!Element.prototype.remove) {
|
||||
|
@ -42,3 +45,5 @@ if (!Element.prototype.remove) {
|
|||
this.parentNode.removeChild(this);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,36 @@ limitations under the License.
|
|||
|
||||
import { StorageError } from "../common.js";
|
||||
|
||||
let needsSyncPromise = false;
|
||||
|
||||
/* should be called on legacy platforms to see
|
||||
if transactions close before draining the microtask queue (IE11 on Windows 7).
|
||||
If this is the case, promises need to be resolved
|
||||
synchronously from the idb request handler to prevent the transaction from closing prematurely.
|
||||
*/
|
||||
export async function checkNeedsSyncPromise() {
|
||||
// important to have it turned off while doing the test,
|
||||
// otherwise reqAsPromise would not fail
|
||||
needsSyncPromise = false;
|
||||
const NAME = "test-idb-needs-sync-promise";
|
||||
const db = await openDatabase(NAME, db => {
|
||||
db.createObjectStore("test", {keyPath: "key"});
|
||||
}, 1);
|
||||
const txn = db.transaction("test", "readonly");
|
||||
try {
|
||||
await reqAsPromise(txn.objectStore("test").get(1));
|
||||
await reqAsPromise(txn.objectStore("test").get(2));
|
||||
} catch (err) {
|
||||
// err.name would be either TransactionInactiveError or InvalidStateError,
|
||||
// but let's not exclude any other failure modes
|
||||
needsSyncPromise = true;
|
||||
}
|
||||
// we could delete the store here,
|
||||
// but let's not create it on every page load on legacy platforms,
|
||||
// and just keep it around
|
||||
return needsSyncPromise;
|
||||
}
|
||||
|
||||
class IDBRequestError extends StorageError {
|
||||
constructor(request) {
|
||||
const source = request?.source;
|
||||
|
@ -38,7 +68,7 @@ export function decodeUint32(str) {
|
|||
}
|
||||
|
||||
export function openDatabase(name, createObjectStore, version) {
|
||||
const req = window.indexedDB.open(name, version);
|
||||
const req = indexedDB.open(name, version);
|
||||
req.onupgradeneeded = (ev) => {
|
||||
const db = ev.target.result;
|
||||
const txn = ev.target.transaction;
|
||||
|
@ -52,11 +82,11 @@ export function reqAsPromise(req) {
|
|||
return new Promise((resolve, reject) => {
|
||||
req.addEventListener("success", event => {
|
||||
resolve(event.target.result);
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
});
|
||||
req.addEventListener("error", () => {
|
||||
reject(new IDBRequestError(req));
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -65,11 +95,11 @@ export function txnAsPromise(txn) {
|
|||
return new Promise((resolve, reject) => {
|
||||
txn.addEventListener("complete", () => {
|
||||
resolve();
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
});
|
||||
txn.addEventListener("abort", () => {
|
||||
reject(new IDBRequestError(txn));
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -79,23 +109,24 @@ export function iterateCursor(cursorRequest, processValue) {
|
|||
return new Promise((resolve, reject) => {
|
||||
cursorRequest.onerror = () => {
|
||||
reject(new IDBRequestError(cursorRequest));
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
};
|
||||
// collect results
|
||||
cursorRequest.onsuccess = (event) => {
|
||||
const cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
resolve(false);
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
return; // end of results
|
||||
}
|
||||
const result = processValue(cursor.value, cursor.key);
|
||||
// TODO: don't use object for result and assume it's jumpTo when not === true/false or undefined
|
||||
const done = result?.done;
|
||||
const jumpTo = result?.jumpTo;
|
||||
|
||||
if (done) {
|
||||
resolve(true);
|
||||
Promise._flush && Promise._flush();
|
||||
needsSyncPromise && Promise._flush && Promise._flush();
|
||||
} else if(jumpTo) {
|
||||
cursor.continue(jumpTo);
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue