123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- /* eslint consistent-this: 0, no-shadow:0, no-eq-null: 0, eqeqeq: 0, no-unused-vars: 0 */
- // Support for asynchronous functions
- "use strict";
- 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); })
- );
- });
- };
|