async.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /* eslint consistent-this: 0, no-shadow:0, no-eq-null: 0, eqeqeq: 0, no-unused-vars: 0 */
  2. // Support for asynchronous functions
  3. "use strict";
  4. var aFrom = require("es5-ext/array/from")
  5. , objectMap = require("es5-ext/object/map")
  6. , mixin = require("es5-ext/object/mixin")
  7. , defineLength = require("es5-ext/function/_define-length")
  8. , nextTick = require("next-tick");
  9. var slice = Array.prototype.slice, apply = Function.prototype.apply, create = Object.create;
  10. require("../lib/registered-extensions").async = function (tbi, conf) {
  11. var waiting = create(null)
  12. , cache = create(null)
  13. , base = conf.memoized
  14. , original = conf.original
  15. , currentCallback
  16. , currentContext
  17. , currentArgs;
  18. // Initial
  19. conf.memoized = defineLength(function (arg) {
  20. var args = arguments, last = args[args.length - 1];
  21. if (typeof last === "function") {
  22. currentCallback = last;
  23. args = slice.call(args, 0, -1);
  24. }
  25. return base.apply(currentContext = this, currentArgs = args);
  26. }, base);
  27. try {
  28. mixin(conf.memoized, base);
  29. } catch (ignore) {}
  30. // From cache (sync)
  31. conf.on("get", function (id) {
  32. var cb, context, args;
  33. if (!currentCallback) return;
  34. // Unresolved
  35. if (waiting[id]) {
  36. if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback];
  37. else waiting[id].push(currentCallback);
  38. currentCallback = null;
  39. return;
  40. }
  41. // Resolved, assure next tick invocation
  42. cb = currentCallback;
  43. context = currentContext;
  44. args = currentArgs;
  45. currentCallback = currentContext = currentArgs = null;
  46. nextTick(function () {
  47. var data;
  48. if (hasOwnProperty.call(cache, id)) {
  49. data = cache[id];
  50. conf.emit("getasync", id, args, context);
  51. apply.call(cb, data.context, data.args);
  52. } else {
  53. // Purged in a meantime, we shouldn't rely on cached value, recall
  54. currentCallback = cb;
  55. currentContext = context;
  56. currentArgs = args;
  57. base.apply(context, args);
  58. }
  59. });
  60. });
  61. // Not from cache
  62. conf.original = function () {
  63. var args, cb, origCb, result;
  64. if (!currentCallback) return apply.call(original, this, arguments);
  65. args = aFrom(arguments);
  66. cb = function self(err) {
  67. var cb, args, id = self.id;
  68. if (id == null) {
  69. // Shouldn't happen, means async callback was called sync way
  70. nextTick(apply.bind(self, this, arguments));
  71. return undefined;
  72. }
  73. delete self.id;
  74. cb = waiting[id];
  75. delete waiting[id];
  76. if (!cb) {
  77. // Already processed,
  78. // outcome of race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb)
  79. return undefined;
  80. }
  81. args = aFrom(arguments);
  82. if (conf.has(id)) {
  83. if (err) {
  84. conf.delete(id);
  85. } else {
  86. cache[id] = { context: this, args: args };
  87. conf.emit("setasync", id, typeof cb === "function" ? 1 : cb.length);
  88. }
  89. }
  90. if (typeof cb === "function") {
  91. result = apply.call(cb, this, args);
  92. } else {
  93. cb.forEach(function (cb) {
  94. result = apply.call(cb, this, args);
  95. }, this);
  96. }
  97. return result;
  98. };
  99. origCb = currentCallback;
  100. currentCallback = currentContext = currentArgs = null;
  101. args.push(cb);
  102. result = apply.call(original, this, args);
  103. cb.cb = origCb;
  104. currentCallback = cb;
  105. return result;
  106. };
  107. // After not from cache call
  108. conf.on("set", function (id) {
  109. if (!currentCallback) {
  110. conf.delete(id);
  111. return;
  112. }
  113. if (waiting[id]) {
  114. // Race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb)
  115. if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback.cb];
  116. else waiting[id].push(currentCallback.cb);
  117. } else {
  118. waiting[id] = currentCallback.cb;
  119. }
  120. delete currentCallback.cb;
  121. currentCallback.id = id;
  122. currentCallback = null;
  123. });
  124. // On delete
  125. conf.on("delete", function (id) {
  126. var result;
  127. // If false, we don't have value yet, so we assume that intention is not
  128. // to memoize this call. After value is obtained we don't cache it but
  129. // gracefully pass to callback
  130. if (hasOwnProperty.call(waiting, id)) return;
  131. if (!cache[id]) return;
  132. result = cache[id];
  133. delete cache[id];
  134. conf.emit("deleteasync", id, slice.call(result.args, 1));
  135. });
  136. // On clear
  137. conf.on("clear", function () {
  138. var oldCache = cache;
  139. cache = create(null);
  140. conf.emit(
  141. "clearasync",
  142. objectMap(oldCache, function (data) {
  143. return slice.call(data.args, 1);
  144. })
  145. );
  146. });
  147. };