promise.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /* eslint max-statements: 0 */
  2. // Support for functions returning promise
  3. "use strict";
  4. var objectMap = require("es5-ext/object/map")
  5. , primitiveSet = require("es5-ext/object/primitive-set")
  6. , ensureString = require("es5-ext/object/validate-stringifiable-value")
  7. , toShortString = require("es5-ext/to-short-string-representation")
  8. , isPromise = require("is-promise")
  9. , nextTick = require("next-tick");
  10. var create = Object.create
  11. , supportedModes = primitiveSet("then", "then:finally", "done", "done:finally");
  12. require("../lib/registered-extensions").promise = function (mode, conf) {
  13. var waiting = create(null), cache = create(null), promises = create(null);
  14. if (mode === true) {
  15. mode = null;
  16. } else {
  17. mode = ensureString(mode);
  18. if (!supportedModes[mode]) {
  19. throw new TypeError("'" + toShortString(mode) + "' is not valid promise mode");
  20. }
  21. }
  22. // After not from cache call
  23. conf.on("set", function (id, ignore, promise) {
  24. var isFailed = false;
  25. if (!isPromise(promise)) {
  26. // Non promise result
  27. cache[id] = promise;
  28. conf.emit("setasync", id, 1);
  29. return;
  30. }
  31. waiting[id] = 1;
  32. promises[id] = promise;
  33. var onSuccess = function (result) {
  34. var count = waiting[id];
  35. if (isFailed) {
  36. throw new Error(
  37. "Memoizee error: Detected unordered then|done & finally resolution, which " +
  38. "in turn makes proper detection of success/failure impossible (when in " +
  39. "'done:finally' mode)\n" +
  40. "Consider to rely on 'then' or 'done' mode instead."
  41. );
  42. }
  43. if (!count) return; // Deleted from cache before resolved
  44. delete waiting[id];
  45. cache[id] = result;
  46. conf.emit("setasync", id, count);
  47. };
  48. var onFailure = function () {
  49. isFailed = true;
  50. if (!waiting[id]) return; // Deleted from cache (or succeed in case of finally)
  51. delete waiting[id];
  52. delete promises[id];
  53. conf.delete(id);
  54. };
  55. var resolvedMode = mode;
  56. if (!resolvedMode) resolvedMode = "then";
  57. if (resolvedMode === "then") {
  58. promise.then(
  59. function (result) {
  60. nextTick(onSuccess.bind(this, result));
  61. },
  62. function () {
  63. nextTick(onFailure);
  64. }
  65. );
  66. } else if (resolvedMode === "done") {
  67. // Not recommended, as it may mute any eventual "Unhandled error" events
  68. if (typeof promise.done !== "function") {
  69. throw new Error(
  70. "Memoizee error: Retrieved promise does not implement 'done' " +
  71. "in 'done' mode"
  72. );
  73. }
  74. promise.done(onSuccess, onFailure);
  75. } else if (resolvedMode === "done:finally") {
  76. // The only mode with no side effects assuming library does not throw unconditionally
  77. // for rejected promises.
  78. if (typeof promise.done !== "function") {
  79. throw new Error(
  80. "Memoizee error: Retrieved promise does not implement 'done' " +
  81. "in 'done:finally' mode"
  82. );
  83. }
  84. if (typeof promise.finally !== "function") {
  85. throw new Error(
  86. "Memoizee error: Retrieved promise does not implement 'finally' " +
  87. "in 'done:finally' mode"
  88. );
  89. }
  90. promise.done(onSuccess);
  91. promise.finally(onFailure);
  92. }
  93. });
  94. // From cache (sync)
  95. conf.on("get", function (id, args, context) {
  96. var promise;
  97. if (waiting[id]) {
  98. ++waiting[id]; // Still waiting
  99. return;
  100. }
  101. promise = promises[id];
  102. var emit = function () {
  103. conf.emit("getasync", id, args, context);
  104. };
  105. if (isPromise(promise)) {
  106. if (typeof promise.done === "function") promise.done(emit);
  107. else {
  108. promise.then(function () {
  109. nextTick(emit);
  110. });
  111. }
  112. } else {
  113. emit();
  114. }
  115. });
  116. // On delete
  117. conf.on("delete", function (id) {
  118. delete promises[id];
  119. if (waiting[id]) {
  120. delete waiting[id];
  121. return; // Not yet resolved
  122. }
  123. if (!hasOwnProperty.call(cache, id)) return;
  124. var result = cache[id];
  125. delete cache[id];
  126. conf.emit("deleteasync", id, [result]);
  127. });
  128. // On clear
  129. conf.on("clear", function () {
  130. var oldCache = cache;
  131. cache = create(null);
  132. waiting = create(null);
  133. promises = create(null);
  134. conf.emit(
  135. "clearasync",
  136. objectMap(oldCache, function (data) {
  137. return [data];
  138. })
  139. );
  140. });
  141. };