<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <script type="text/javascript">
        function reqAsPromise(req) {
            return new Promise(function (resolve, reject) {
                req.onsuccess = function() {
                    resolve(req.result);
                };
                req.onerror = function(e) {
                    reject(new Error("IDB request failed: " + req.error));
                };
            });
        }
        function txnAsPromise(txn) {
            return new Promise(function (resolve, reject) {
                txn.oncomplete = function() {
                    resolve(txn);
                };
                txn.onabort = function(e) {
                    reject(new Error("Transaction got aborted: " + txn.error));
                };
            });
        }

        const BrowserMutationObserver = window.MutationObserver || window.WebKitMutationObserver;

        function useMutationObserver(flush) {
          let iterations = 0;
          const observer = new BrowserMutationObserver(flush);
          const node = document.createTextNode('');
          observer.observe(node, { characterData: true });

          return () => {
            node.data = (iterations = ++iterations % 2);
          };
        }

        const wait = (function() {
            let resolve = null;
            const trigger = useMutationObserver(() => {
                resolve();
            });
            return () => {
                return new Promise(r => {
                    resolve = r;
                    trigger();
                });
            };
        })();
        
        
        var _resolve = Promise.resolve.bind(Promise);
        var _then = Promise.prototype.then;

        async function delay() {
            return Promise.resolve();
            // two consecutive macro tasks
            //await new Promise(r => setImmediate(r));
            // the next macro task will now be the complete event of the txn,
            // so schedule another macro task to execute after that
            //await new Promise(r => setImmediate(r));
            //return;
            // for (let i = 0; i < 1000; i+=1) {
            //         console.log("await...");
            //         await wait();
            //     }
            let p = _resolve(0);
            for (let i=0;i<10;++i) {
                p = _then.call(p, x => x + 1);
            }
            let result = await p;
            console.log("Result: "+ result + " (should be 10)");
        }

        class Storage {
            constructor(databaseName) {
                this._databaseName = databaseName;
                this._database = null;
            }

            open() {
                const req = window.indexedDB.open(this._databaseName);
                const self = this;
                req.onupgradeneeded = function(ev) {
                    const db = ev.target.result;
                    const oldVersion = ev.oldVersion;
                    self._createStores(db, oldVersion);
                }; 
                return reqAsPromise(req).then(function() {
                    self._database = req.result;
                });
            }

            openTxn(mode, storeName) {
                const txn = this._database.transaction([storeName], mode);
                txn.addEventListener("complete", () => {
                    console.info(`transaction ${mode} for ${storeName} completed`);
                });
                txn.addEventListener("abort", e => {
                    console.warn(`transaction ${mode} for ${storeName} aborted`, e.target.error);
                });
                return txn;
            }

            _createStores(db) {
                db.createObjectStore("foos", {keyPath: "id"});
            }
        }

        async function getAll(store, depth = 0) {
            if (depth < 15) {
                return await getAll(store, depth + 1);
            } 
            const request = store.openCursor();
            const results = [];
            return await new Promise(function(resolve, reject) {
                request.onsuccess = function(event) {
                    const cursor = event.target.result;
                    if(cursor) {
                        results.push(cursor.value);
                        cursor.continue();
                    } else {
                        resolve(results);
                        Promise.flushQueue && Promise.flushQueue();
                    }
                };
                request.onerror = function(e) {
                    reject(new Error("IDB request failed: " + e.target.error.message));
                    Promise.flushQueue && Promise.flushQueue();
                };
            });
        }

        async function main() {
            try {
                let storage = new Storage("idb-promises");
                await storage.open();
                //await reqAsPromise(storage.openTxn("readwrite", "foos").objectStore("foos").clear());

                for (let i = 0; i < 10; i += 1) {
                    storage.openTxn("readonly", "foos").objectStore("foos").get(5);
                    //console.log("from readtxn", await reqAsPromise(storage.openTxn("readonly", "foos").objectStore("foos").get(5)));
                    const txn = storage.openTxn("readwrite", "foos");
                    const store = txn.objectStore("foos");
                    console.log("writing the foos");
                    store.put({id: 5, name: "foo"});
                    store.put({id: 6, name: "bar"});
                    store.put({id: 7, name: "bazzz"});
                    await delay();
                    console.log("reading the foos");
                    console.log("5", await reqAsPromise(store.get(5)));
                    console.log("6", await reqAsPromise(store.get(6)));
                    console.log("7", await reqAsPromise(store.get(7)));
                    // await txnAsPromise(txn);
                }
            } catch(err) {
                console.error(err);
            };
        }
        main();
    </script>
</body>
</html>