promise.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. var nextTickFailure = function () { nextTick(onFailure); };
  59. // Eventual finally needs to be attached to non rejected promise
  60. // (so we not force propagation of unhandled rejection)
  61. promise = promise.then(function (result) {
  62. nextTick(onSuccess.bind(this, result));
  63. }, nextTickFailure);
  64. // If `finally` is a function we attach to it to remove cancelled promises.
  65. if (typeof promise.finally === "function") {
  66. promise.finally(nextTickFailure);
  67. }
  68. } else if (resolvedMode === "done") {
  69. // Not recommended, as it may mute any eventual "Unhandled error" events
  70. if (typeof promise.done !== "function") {
  71. throw new Error(
  72. "Memoizee error: Retrieved promise does not implement 'done' " +
  73. "in 'done' mode"
  74. );
  75. }
  76. promise.done(onSuccess, onFailure);
  77. } else if (resolvedMode === "done:finally") {
  78. // The only mode with no side effects assuming library does not throw unconditionally
  79. // for rejected promises.
  80. if (typeof promise.done !== "function") {
  81. throw new Error(
  82. "Memoizee error: Retrieved promise does not implement 'done' " +
  83. "in 'done:finally' mode"
  84. );
  85. }
  86. if (typeof promise.finally !== "function") {
  87. throw new Error(
  88. "Memoizee error: Retrieved promise does not implement 'finally' " +
  89. "in 'done:finally' mode"
  90. );
  91. }
  92. promise.done(onSuccess);
  93. promise.finally(onFailure);
  94. }
  95. });
  96. // From cache (sync)
  97. conf.on("get", function (id, args, context) {
  98. var promise;
  99. if (waiting[id]) {
  100. ++waiting[id]; // Still waiting
  101. return;
  102. }
  103. promise = promises[id];
  104. var emit = function () { conf.emit("getasync", id, args, context); };
  105. if (isPromise(promise)) {
  106. if (typeof promise.done === "function") promise.done(emit);
  107. else {
  108. promise.then(function () { nextTick(emit); });
  109. }
  110. } else {
  111. emit();
  112. }
  113. });
  114. // On delete
  115. conf.on("delete", function (id) {
  116. delete promises[id];
  117. if (waiting[id]) {
  118. delete waiting[id];
  119. return; // Not yet resolved
  120. }
  121. if (!hasOwnProperty.call(cache, id)) return;
  122. var result = cache[id];
  123. delete cache[id];
  124. conf.emit("deleteasync", id, [result]);
  125. });
  126. // On clear
  127. conf.on("clear", function () {
  128. var oldCache = cache;
  129. cache = create(null);
  130. waiting = create(null);
  131. promises = create(null);
  132. conf.emit("clearasync", objectMap(oldCache, function (data) { return [data]; }));
  133. });
  134. };