UNPKG

ember-cli-ajh

Version:

Command line tool for developing ambitious ember.js apps

147 lines (134 loc) 4.15 kB
// Support for asynchronous functions 'use strict'; var aFrom = require('es5-ext/array/from') , mixin = require('es5-ext/object/mixin') , defineLength = require('es5-ext/function/_define-length') , nextTick = require('next-tick') , slice = Array.prototype.slice , apply = Function.prototype.apply, create = Object.create , hasOwnProperty = Object.prototype.hasOwnProperty; 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; } 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; } 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, result); }); // On clear conf.on('clear', function () { var oldCache = cache; cache = create(null); conf.emit('clearasync', oldCache); }); };