ember-cli-ajh
Version:
Command line tool for developing ambitious ember.js apps
147 lines (134 loc) • 4.15 kB
JavaScript
// Support for asynchronous functions
;
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);
});
};