Spaces:
Runtime error
Runtime error
| /* eslint consistent-this: 0, no-shadow:0, no-eq-null: 0, eqeqeq: 0, no-unused-vars: 0 */ | |
| // Support for asynchronous functions | |
| ; | |
| var aFrom = require("es5-ext/array/from") | |
| , objectMap = require("es5-ext/object/map") | |
| , mixin = require("es5-ext/object/mixin") | |
| , defineLength = require("es5-ext/function/_define-length") | |
| , nextTick = require("next-tick"); | |
| var slice = Array.prototype.slice, apply = Function.prototype.apply, create = Object.create; | |
| require("../lib/registered-extensions").async = function (tbi, conf) { | |
| var waiting = create(null) | |
| , cache = create(null) | |
| , base = conf.memoized | |
| , original = conf.original | |
| , currentCallback | |
| , currentContext | |
| , currentArgs; | |
| // Initial | |
| conf.memoized = defineLength(function (arg) { | |
| var args = arguments, last = args[args.length - 1]; | |
| if (typeof last === "function") { | |
| currentCallback = last; | |
| args = slice.call(args, 0, -1); | |
| } | |
| return base.apply(currentContext = this, currentArgs = args); | |
| }, base); | |
| try { mixin(conf.memoized, base); } | |
| catch (ignore) {} | |
| // From cache (sync) | |
| conf.on("get", function (id) { | |
| var cb, context, args; | |
| if (!currentCallback) return; | |
| // Unresolved | |
| if (waiting[id]) { | |
| if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback]; | |
| else waiting[id].push(currentCallback); | |
| currentCallback = null; | |
| return; | |
| } | |
| // Resolved, assure next tick invocation | |
| cb = currentCallback; | |
| context = currentContext; | |
| args = currentArgs; | |
| currentCallback = currentContext = currentArgs = null; | |
| nextTick(function () { | |
| var data; | |
| if (hasOwnProperty.call(cache, id)) { | |
| data = cache[id]; | |
| conf.emit("getasync", id, args, context); | |
| apply.call(cb, data.context, data.args); | |
| } else { | |
| // Purged in a meantime, we shouldn't rely on cached value, recall | |
| currentCallback = cb; | |
| currentContext = context; | |
| currentArgs = args; | |
| base.apply(context, args); | |
| } | |
| }); | |
| }); | |
| // Not from cache | |
| conf.original = function () { | |
| var args, cb, origCb, result; | |
| if (!currentCallback) return apply.call(original, this, arguments); | |
| args = aFrom(arguments); | |
| cb = function self(err) { | |
| var cb, args, id = self.id; | |
| if (id == null) { | |
| // Shouldn't happen, means async callback was called sync way | |
| nextTick(apply.bind(self, this, arguments)); | |
| return undefined; | |
| } | |
| delete self.id; | |
| cb = waiting[id]; | |
| delete waiting[id]; | |
| if (!cb) { | |
| // Already processed, | |
| // outcome of race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb) | |
| return undefined; | |
| } | |
| args = aFrom(arguments); | |
| if (conf.has(id)) { | |
| if (err) { | |
| conf.delete(id); | |
| } else { | |
| cache[id] = { context: this, args: args }; | |
| conf.emit("setasync", id, typeof cb === "function" ? 1 : cb.length); | |
| } | |
| } | |
| if (typeof cb === "function") { | |
| result = apply.call(cb, this, args); | |
| } else { | |
| cb.forEach(function (cb) { result = apply.call(cb, this, args); }, this); | |
| } | |
| return result; | |
| }; | |
| origCb = currentCallback; | |
| currentCallback = currentContext = currentArgs = null; | |
| args.push(cb); | |
| result = apply.call(original, this, args); | |
| cb.cb = origCb; | |
| currentCallback = cb; | |
| return result; | |
| }; | |
| // After not from cache call | |
| conf.on("set", function (id) { | |
| if (!currentCallback) { | |
| conf.delete(id); | |
| return; | |
| } | |
| if (waiting[id]) { | |
| // Race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb) | |
| if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback.cb]; | |
| else waiting[id].push(currentCallback.cb); | |
| } else { | |
| waiting[id] = currentCallback.cb; | |
| } | |
| delete currentCallback.cb; | |
| currentCallback.id = id; | |
| currentCallback = null; | |
| }); | |
| // On delete | |
| conf.on("delete", function (id) { | |
| var result; | |
| // If false, we don't have value yet, so we assume that intention is not | |
| // to memoize this call. After value is obtained we don't cache it but | |
| // gracefully pass to callback | |
| if (hasOwnProperty.call(waiting, id)) return; | |
| if (!cache[id]) return; | |
| result = cache[id]; | |
| delete cache[id]; | |
| conf.emit("deleteasync", id, slice.call(result.args, 1)); | |
| }); | |
| // On clear | |
| conf.on("clear", function () { | |
| var oldCache = cache; | |
| cache = create(null); | |
| conf.emit( | |
| "clearasync", objectMap(oldCache, function (data) { return slice.call(data.args, 1); }) | |
| ); | |
| }); | |
| }; | |