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); }) | |
); | |
}); | |
}; | |