async.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 { mixin(conf.memoized, base); }
  28. catch (ignore) {}
  29. // From cache (sync)
  30. conf.on("get", function (id) {
  31. var cb, context, args;
  32. if (!currentCallback) return;
  33. // Unresolved
  34. if (waiting[id]) {
  35. if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback];
  36. else waiting[id].push(currentCallback);
  37. currentCallback = null;
  38. return;
  39. }
  40. // Resolved, assure next tick invocation
  41. cb = currentCallback;
  42. context = currentContext;
  43. args = currentArgs;
  44. currentCallback = currentContext = currentArgs = null;
  45. nextTick(function () {
  46. var data;
  47. if (hasOwnProperty.call(cache, id)) {
  48. data = cache[id];
  49. conf.emit("getasync", id, args, context);
  50. apply.call(cb, data.context, data.args);
  51. } else {
  52. // Purged in a meantime, we shouldn't rely on cached value, recall
  53. currentCallback = cb;
  54. currentContext = context;
  55. currentArgs = args;
  56. base.apply(context, args);
  57. }
  58. });
  59. });
  60. // Not from cache
  61. conf.original = function () {
  62. var args, cb, origCb, result;
  63. if (!currentCallback) return apply.call(original, this, arguments);
  64. args = aFrom(arguments);
  65. cb = function self(err) {
  66. var cb, args, id = self.id;
  67. if (id == null) {
  68. // Shouldn't happen, means async callback was called sync way
  69. nextTick(apply.bind(self, this, arguments));
  70. return undefined;
  71. }
  72. delete self.id;
  73. cb = waiting[id];
  74. delete waiting[id];
  75. if (!cb) {
  76. // Already processed,
  77. // outcome of race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb)
  78. return undefined;
  79. }
  80. args = aFrom(arguments);
  81. if (conf.has(id)) {
  82. if (err) {
  83. conf.delete(id);
  84. } else {
  85. cache[id] = { context: this, args: args };
  86. conf.emit("setasync", id, typeof cb === "function" ? 1 : cb.length);
  87. }
  88. }
  89. if (typeof cb === "function") {
  90. result = apply.call(cb, this, args);
  91. } else {
  92. cb.forEach(function (cb) { result = apply.call(cb, this, args); }, this);
  93. }
  94. return result;
  95. };
  96. origCb = currentCallback;
  97. currentCallback = currentContext = currentArgs = null;
  98. args.push(cb);
  99. result = apply.call(original, this, args);
  100. cb.cb = origCb;
  101. currentCallback = cb;
  102. return result;
  103. };
  104. // After not from cache call
  105. conf.on("set", function (id) {
  106. if (!currentCallback) {
  107. conf.delete(id);
  108. return;
  109. }
  110. if (waiting[id]) {
  111. // Race condition: asyncFn(1, cb), asyncFn.clear(), asyncFn(1, cb)
  112. if (typeof waiting[id] === "function") waiting[id] = [waiting[id], currentCallback.cb];
  113. else waiting[id].push(currentCallback.cb);
  114. } else {
  115. waiting[id] = currentCallback.cb;
  116. }
  117. delete currentCallback.cb;
  118. currentCallback.id = id;
  119. currentCallback = null;
  120. });
  121. // On delete
  122. conf.on("delete", function (id) {
  123. var result;
  124. // If false, we don't have value yet, so we assume that intention is not
  125. // to memoize this call. After value is obtained we don't cache it but
  126. // gracefully pass to callback
  127. if (hasOwnProperty.call(waiting, id)) return;
  128. if (!cache[id]) return;
  129. result = cache[id];
  130. delete cache[id];
  131. conf.emit("deleteasync", id, slice.call(result.args, 1));
  132. });
  133. // On clear
  134. conf.on("clear", function () {
  135. var oldCache = cache;
  136. cache = create(null);
  137. conf.emit(
  138. "clearasync", objectMap(oldCache, function (data) { return slice.call(data.args, 1); })
  139. );
  140. });
  141. };