Spaces:
Runtime error
Runtime error
/* eslint max-statements: 0 */ | |
// Support for functions returning promise | |
; | |
var objectMap = require("es5-ext/object/map") | |
, primitiveSet = require("es5-ext/object/primitive-set") | |
, ensureString = require("es5-ext/object/validate-stringifiable-value") | |
, toShortString = require("es5-ext/to-short-string-representation") | |
, isPromise = require("is-promise") | |
, nextTick = require("next-tick"); | |
var create = Object.create | |
, supportedModes = primitiveSet("then", "then:finally", "done", "done:finally"); | |
require("../lib/registered-extensions").promise = function (mode, conf) { | |
var waiting = create(null), cache = create(null), promises = create(null); | |
if (mode === true) { | |
mode = null; | |
} else { | |
mode = ensureString(mode); | |
if (!supportedModes[mode]) { | |
throw new TypeError("'" + toShortString(mode) + "' is not valid promise mode"); | |
} | |
} | |
// After not from cache call | |
conf.on("set", function (id, ignore, promise) { | |
var isFailed = false; | |
if (!isPromise(promise)) { | |
// Non promise result | |
cache[id] = promise; | |
conf.emit("setasync", id, 1); | |
return; | |
} | |
waiting[id] = 1; | |
promises[id] = promise; | |
var onSuccess = function (result) { | |
var count = waiting[id]; | |
if (isFailed) { | |
throw new Error( | |
"Memoizee error: Detected unordered then|done & finally resolution, which " + | |
"in turn makes proper detection of success/failure impossible (when in " + | |
"'done:finally' mode)\n" + | |
"Consider to rely on 'then' or 'done' mode instead." | |
); | |
} | |
if (!count) return; // Deleted from cache before resolved | |
delete waiting[id]; | |
cache[id] = result; | |
conf.emit("setasync", id, count); | |
}; | |
var onFailure = function () { | |
isFailed = true; | |
if (!waiting[id]) return; // Deleted from cache (or succeed in case of finally) | |
delete waiting[id]; | |
delete promises[id]; | |
conf.delete(id); | |
}; | |
var resolvedMode = mode; | |
if (!resolvedMode) resolvedMode = "then"; | |
if (resolvedMode === "then") { | |
var nextTickFailure = function () { nextTick(onFailure); }; | |
// Eventual finally needs to be attached to non rejected promise | |
// (so we not force propagation of unhandled rejection) | |
promise = promise.then(function (result) { | |
nextTick(onSuccess.bind(this, result)); | |
}, nextTickFailure); | |
// If `finally` is a function we attach to it to remove cancelled promises. | |
if (typeof promise.finally === "function") { | |
promise.finally(nextTickFailure); | |
} | |
} else if (resolvedMode === "done") { | |
// Not recommended, as it may mute any eventual "Unhandled error" events | |
if (typeof promise.done !== "function") { | |
throw new Error( | |
"Memoizee error: Retrieved promise does not implement 'done' " + | |
"in 'done' mode" | |
); | |
} | |
promise.done(onSuccess, onFailure); | |
} else if (resolvedMode === "done:finally") { | |
// The only mode with no side effects assuming library does not throw unconditionally | |
// for rejected promises. | |
if (typeof promise.done !== "function") { | |
throw new Error( | |
"Memoizee error: Retrieved promise does not implement 'done' " + | |
"in 'done:finally' mode" | |
); | |
} | |
if (typeof promise.finally !== "function") { | |
throw new Error( | |
"Memoizee error: Retrieved promise does not implement 'finally' " + | |
"in 'done:finally' mode" | |
); | |
} | |
promise.done(onSuccess); | |
promise.finally(onFailure); | |
} | |
}); | |
// From cache (sync) | |
conf.on("get", function (id, args, context) { | |
var promise; | |
if (waiting[id]) { | |
++waiting[id]; // Still waiting | |
return; | |
} | |
promise = promises[id]; | |
var emit = function () { conf.emit("getasync", id, args, context); }; | |
if (isPromise(promise)) { | |
if (typeof promise.done === "function") promise.done(emit); | |
else { | |
promise.then(function () { nextTick(emit); }); | |
} | |
} else { | |
emit(); | |
} | |
}); | |
// On delete | |
conf.on("delete", function (id) { | |
delete promises[id]; | |
if (waiting[id]) { | |
delete waiting[id]; | |
return; // Not yet resolved | |
} | |
if (!hasOwnProperty.call(cache, id)) return; | |
var result = cache[id]; | |
delete cache[id]; | |
conf.emit("deleteasync", id, [result]); | |
}); | |
// On clear | |
conf.on("clear", function () { | |
var oldCache = cache; | |
cache = create(null); | |
waiting = create(null); | |
promises = create(null); | |
conf.emit("clearasync", objectMap(oldCache, function (data) { return [data]; })); | |
}); | |
}; | |