Skip to content

Commit

Permalink
Hopefully resolves #315 by introducing an alternate tranx method that…
Browse files Browse the repository at this point in the history
… replaces window.Promise withing the transaction scope only. NOTE: It replaces window.Promise temporarly only for code that executes within the transaction scope, so it won't disturb neither Zone.js or other libraries that depend on window.Promise. This will only affect your DB-facing code and code you call from withing your transaction scope.
  • Loading branch information
dfahlander committed Oct 7, 2016
1 parent 95a7ddc commit 203143f
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 88 deletions.
20 changes: 20 additions & 0 deletions src/Dexie.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
/// <summary>
Expand Down
105 changes: 17 additions & 88 deletions src/Promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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 = [];
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -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);
}
Expand Down Expand Up @@ -602,17 +581,18 @@ 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) {
errorCatcher && errorCatcher(e);
} 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();
}
Expand All @@ -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.
Expand All @@ -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();
}
}
}
Expand All @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions test/tests-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
51 changes: 51 additions & 0 deletions test/tests-tranx.js
Original file line number Diff line number Diff line change
@@ -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');
});
});

0 comments on commit 203143f

Please sign in to comment.