diff --git a/src/Dexie.js b/src/Dexie.js index f3fa7d7a87..72a013429f 100644 --- a/src/Dexie.js +++ b/src/Dexie.js @@ -696,6 +696,26 @@ export default function Dexie(dbName, options) { db.on("populate").fire(db._createTransaction(READWRITE, dbStoreNames, globalSchema)); db.on("error").fire(new Error()); }); + + this.tranx = function (mode, tableInstances, scopeFunc) { + var l = arguments.length, + args = new Array(l), + i = l; + while (i--) args[i] = arguments[i]; + var func = args[l-1]; + args[l-1] = function () { + PSD.onenter = function () { + this.Promise = _global.Promise; + _global.Promise = Promise; + } + PSD.onleave = function () { + _global.Promise = this.Promise; + } + PSD.onenter(); + return func.call(this, this); // Give transaction as first argument instead of table instances. + }; + return this.transaction.apply(this, args); + } this.transaction = function (mode, tableInstances, scopeFunc) { /// diff --git a/src/Promise.js b/src/Promise.js index 90b36e6044..b3f813941c 100644 --- a/src/Promise.js +++ b/src/Promise.js @@ -94,6 +94,8 @@ export var globalPSD = { ref: 0, unhandleds: [], onunhandled: globalError, + onenter: null, + onleave: null, //env: null, // Will be set whenever leaving a scope using wrappers.snapshot() finalize: function () { this.unhandleds.forEach(uh => { @@ -110,30 +112,6 @@ export var microtickQueue = []; // Callbacks to call in this or next physical ti export var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick. export var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick. -// Wrappers are not being used yet. Their framework is functioning and can be used -// to replace environment during a PSD scope (a.k.a. 'zone'). -/* **KEEP** export var wrappers = (() => { - var wrappers = []; - - return { - snapshot: () => { - var i = wrappers.length, - result = new Array(i); - while (i--) result[i] = wrappers[i].snapshot(); - return result; - }, - restore: values => { - var i = wrappers.length; - while (i--) wrappers[i].restore(values[i]); - }, - wrap: () => wrappers.map(w => w.wrap()), - add: wrapper => { - wrappers.push(wrapper); - } - }; -})(); -*/ - export default function Promise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); this._listeners = []; @@ -431,9 +409,9 @@ function callListener (cb, promise, listener) { var psd = listener.psd; try { if (psd !== outerScope) { - // **KEEP** outerScope.env = wrappers.snapshot(); // Snapshot outerScope's environment. PSD = psd; - // **KEEP** wrappers.restore(psd.env); // Restore PSD's environment. + outerScope.onleave && outerScope.onleave(); + psd.onenter && psd.onenter(); } // Set static variable currentFulfiller to the promise that is being fullfilled, @@ -459,7 +437,8 @@ function callListener (cb, promise, listener) { // Restore PSD, env and currentFulfiller. if (psd !== outerScope) { PSD = outerScope; - // **KEEP** wrappers.restore(outerScope.env); // Restore outerScope's environment + psd.onleave && psd.onleave(); + outerScope.onenter && outerScope.onenter(); } currentFulfiller = null; if (--numScheduledCalls === 0) finalizePhysicalTick(); @@ -481,7 +460,7 @@ function getStack (promise, stacks, limit) { stack = prettyStack(failure, 0); } else { errorName = failure; // If error is undefined or null, show that. - message = "" + message = ""; } stacks.push(errorName + (message ? ": " + message : "") + stack); } @@ -602,9 +581,9 @@ export function wrap (fn, errorCatcher) { try { if (outerScope !== psd) { - // **KEEP** outerScope.env = wrappers.snapshot(); // Snapshot outerScope's environment PSD = psd; - // **KEEP** wrappers.restore(psd.env); // Restore PSD's environment. + outerScope.onleave && outerScope.onleave(); + psd.onenter && psd.onenter(); } return fn.apply(this, arguments); } catch (e) { @@ -612,7 +591,8 @@ export function wrap (fn, errorCatcher) { } finally { if (outerScope !== psd) { PSD = outerScope; - // **KEEP** wrappers.restore(outerScope.env); // Restore outerScope's environment + psd.onleave && psd.onleave(); + outerScope.onenter && outerScope.onenter(); } if (wasRootExec) endMicroTickScope(); } @@ -625,7 +605,8 @@ export function newScope (fn, a1, a2, a3) { psd.parent = parent; psd.ref = 0; psd.global = false; - // **KEEP** psd.env = wrappers.wrap(psd); + psd.onenter = null; + psd.onleave = null; // unhandleds and onunhandled should not be specifically set here. // Leave them on parent prototype. @@ -644,15 +625,16 @@ export function usePSD (psd, fn, a1, a2, a3) { var outerScope = PSD; try { if (psd !== outerScope) { - // **KEEP** outerScope.env = wrappers.snapshot(); // snapshot outerScope's environment. PSD = psd; - // **KEEP** wrappers.restore(psd.env); // Restore PSD's environment. + outerScope.onleave && outerScope.onleave(); + psd.onenter && psd.onenter(); } return fn(a1, a2, a3); } finally { if (psd !== outerScope) { PSD = outerScope; - // **KEEP** wrappers.restore(outerScope.env); // Restore outerScope's environment. + psd.onleave && psd.onleave(); + outerScope.onenter && outerScope.onenter(); } } } @@ -667,59 +649,6 @@ function globalError(err, promise) { } catch (e) {} } -/* **KEEP** - -export function wrapPromise(PromiseClass) { - var proto = PromiseClass.prototype; - var origThen = proto.then; - - wrappers.add({ - snapshot: () => proto.then, - restore: value => {proto.then = value;}, - wrap: () => patchedThen - }); - - function patchedThen (onFulfilled, onRejected) { - var promise = this; - var onFulfilledProxy = wrap(function(value){ - var rv = value; - if (onFulfilled) { - rv = onFulfilled(rv); - if (rv && typeof rv.then === 'function') rv.then(); // Intercept that promise as well. - } - --PSD.ref || PSD.finalize(); - return rv; - }); - var onRejectedProxy = wrap(function(err){ - promise._$err = err; - var unhandleds = PSD.unhandleds; - var idx = unhandleds.length, - rv; - while (idx--) if (unhandleds[idx]._$err === err) break; - if (onRejected) { - if (idx !== -1) unhandleds.splice(idx, 1); // Mark as handled. - rv = onRejected(err); - if (rv && typeof rv.then === 'function') rv.then(); // Intercept that promise as well. - } else { - if (idx === -1) unhandleds.push(promise); - rv = PromiseClass.reject(err); - rv._$nointercept = true; // Prohibit eternal loop. - } - --PSD.ref || PSD.finalize(); - return rv; - }); - - if (this._$nointercept) return origThen.apply(this, arguments); - ++PSD.ref; - return origThen.call(this, onFulfilledProxy, onRejectedProxy); - } -} - -// Global Promise wrapper -if (_global.Promise) wrapPromise(_global.Promise); - -*/ - doFakeAutoComplete(() => { // Simplify the job for VS Intellisense. This piece of code is one of the keys to the new marvellous intellisense support in Dexie. asap = (fn, args) => { diff --git a/test/tests-all.js b/test/tests-all.js index 2e0c4be494..9f10802f1e 100644 --- a/test/tests-all.js +++ b/test/tests-all.js @@ -7,6 +7,7 @@ import "./tests-table.js"; import "./tests-extendability.js"; import "./tests-promise.js"; import "./tests-transaction.js"; +import "./tests-tranx.js"; import "./tests-open.js"; import "./tests-exception-handling.js"; import "./tests-upgrading.js"; diff --git a/test/tests-tranx.js b/test/tests-tranx.js new file mode 100644 index 0000000000..e1ea79f2df --- /dev/null +++ b/test/tests-tranx.js @@ -0,0 +1,51 @@ +import Dexie from 'dexie'; +import {module, stop, start, asyncTest, equal, ok} from 'QUnit'; +import {resetDatabase, spawnedTest} from './dexie-unittest-utils'; + +"use strict"; + +var db = new Dexie("TestDBTranx"); +db.version(1).stores({ + items: "id" +}); + +module("tranx", { + setup: function () { + stop(); + resetDatabase(db).catch(function (e) { + ok(false, "Error resetting database: " + e.stack); + }).finally(start); + }, + teardown: function () { + } +}); + +asyncTest("Should be able to use global Promise within transaction scopes", function(){ + db.tranx('rw', db.items, trans => { + return window.Promise.resolve().then(()=> { + ok(Dexie.currentTransaction == trans, "Transaction scopes should persist through Promise.resolve()"); + return db.items.add({ id: "foobar" }); + }).then(()=>{ + return Promise.resolve(); + }).then(()=>{ + ok(Dexie.currentTransaction == trans, "Transaction scopes should persist through Promise.resolve()"); + return db.items.get('foobar'); + }); + }).then (function(foobar){ + equal(foobar.id, 'foobar', "Transaction should have lived throughout the Promise.resolve() chain"); + }).catch (e => { + ok(false, `Error: ${e.stack || e}`); + }).finally(start); +}); + + +spawnedTest("Should use Promise.all where applicable", function* (){ + yield db.tranx('rw', db.items, function* () { + let x = yield Promise.resolve(3); + yield db.items.bulkAdd([{id: 'a'}, {id: 'b'}]); + let all = yield Promise.all(db.items.get('a'), db.items.get('b')); + equal (all.length, 2); + equal (all[0].id, 'a'); + equal (all[1].id, 'b'); + }); +});