Source: util/sync-promise.js

  1. /**
  2. * Copyright (C) 2015-2018 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Lesser General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. * A copy of the GNU Lesser General Public License is in the file COPYING.
  18. */
  19. /** @ignore */
  20. var NdnCommon = require('./ndn-common.js').NdnCommon;
  21. /**
  22. * A SyncPromise is a promise which is immediately fulfilled or rejected, used
  23. * to return a promise in synchronous code.
  24. * This private constructor creates a SyncPromise fulfilled or rejected with the
  25. * given value. You should normally not call this constructor but call
  26. * SyncPromise.resolve or SyncPromise.reject. Note that we don't need a
  27. * constructor like SyncPromise(function(resolve, reject)) because this would be
  28. * for scheduling the function to be called later, which we don't do.
  29. * @param {any} value If isRejected is false, this is the value of the fulfilled
  30. * promise, else if isRejected is true, this is the error.
  31. * @param {boolean} isRejected True to create a promise in the rejected state,
  32. * where value is the error.
  33. * @constructor
  34. */
  35. var SyncPromise = function SyncPromise(value, isRejected)
  36. {
  37. this.value = value;
  38. this.isRejected = isRejected;
  39. };
  40. exports.SyncPromise = SyncPromise;
  41. /**
  42. * If this promise is fulfilled, immediately call onFulfilled with the fulfilled
  43. * value as described below. Otherwise, if this promise is rejected, immediately
  44. * call onRejected with the error as described below.
  45. * @param {function} (optional) onFulfilled If this promise is fulfilled, this
  46. * calls onFulfilled(value) with the value of this promise and returns the
  47. * result. The function should return a promise. To use all synchronous code,
  48. * onFulfilled should return SyncPromise.resolve(newValue).
  49. * @param {function} (optional) onRejected If this promise is rejected, this
  50. * calls onRejected(err) with the error value of this promise and returns the
  51. * result. The function should return a promise. To use all synchronous code,
  52. * onFulfilled should return SyncPromise.resolve(newValue) (or throw an
  53. * exception).
  54. * @return {Promise|SyncPromise} If this promise is fulfilled, return the result
  55. * of calling onFulfilled(value). Note that this does not create a promise which
  56. * is scheduled to execute later. Rather it immediately calls onFulfilled which
  57. * should return a promise. But if onFulfilled is undefined, simply return this
  58. * promise to pass it forward. If this promise is rejected, return the result of
  59. * calling onRejected(err) with the error value. But if onRejected is undefined,
  60. * simply return this promise to pass it forward. However, if onFulfilled or
  61. * onRejected throws an exception, then return a new SyncPromise in the rejected
  62. * state with the exception.
  63. */
  64. SyncPromise.prototype.then = function(onFulfilled, onRejected)
  65. {
  66. if (this.isRejected) {
  67. if (onRejected) {
  68. try {
  69. return onRejected(this.value);
  70. }
  71. catch(err) {
  72. return new SyncPromise(err, true);
  73. }
  74. }
  75. else
  76. // Pass the error forward.
  77. return this;
  78. }
  79. else {
  80. if (onFulfilled) {
  81. try {
  82. return onFulfilled(this.value);
  83. }
  84. catch(err) {
  85. return new SyncPromise(err, true);
  86. }
  87. }
  88. else
  89. // Pass the fulfilled value forward.
  90. return this;
  91. }
  92. };
  93. /**
  94. * Call this.then(undefined, onRejected) and return the result. If this promise
  95. * is rejected then onRejected will process it. If this promise is fulfilled,
  96. * this simply passes it forward.
  97. */
  98. SyncPromise.prototype.catch = function(onRejected)
  99. {
  100. return this.then(undefined, onRejected);
  101. };
  102. /**
  103. * Return a new SyncPromise which is already fulfilled to the given value.
  104. * @param {any} value The value of the promise.
  105. */
  106. SyncPromise.resolve = function(value)
  107. {
  108. return new SyncPromise(value, false);
  109. };
  110. /**
  111. * Return a new SyncPromise which is already rejected with the given error.
  112. * @param {any} err The error for the rejected promise.
  113. */
  114. SyncPromise.reject = function(err)
  115. {
  116. return new SyncPromise(err, true);
  117. };
  118. /**
  119. * This static method checks if the promise is a SyncPromise and immediately
  120. * returns its value or throws the error if promise is rejected. If promise is
  121. * not a SyncPromise, this throws an exception since it is not possible to
  122. * immediately get the value. This can be used with "promise-based" code which
  123. * you expect to always return a SyncPromise to operate in synchronous mode.
  124. * @param {SyncPromise} promise The SyncPromise with the value to get.
  125. * @return {any} The value of the promise.
  126. * @throws Error If promise is not a SyncPromise.
  127. * @throws any If promise is a SyncPromise in the rejected state, this throws
  128. * the error.
  129. */
  130. SyncPromise.getValue = function(promise)
  131. {
  132. if (promise instanceof SyncPromise) {
  133. if (promise.isRejected)
  134. throw promise.value;
  135. else
  136. return promise.value;
  137. }
  138. else
  139. throw new Error("Cannot return immediately because promise is not a SyncPromise");
  140. };
  141. /**
  142. * This can be called with complete(onComplete, promise) or
  143. * complete(onComplete, onError, promise) to handle both synchronous and
  144. * asynchronous code based on whether the caller supplies the onComlete callback.
  145. * If onComplete is defined, call promise.then with a function which calls
  146. * onComplete(value) when fulfilled (possibly in asynchronous mode). If
  147. * onComplete is undefined, then we are in synchronous mode so return
  148. * SyncPromise.getValue(promise) which will throw an exception if the promise is
  149. * not a SyncPromise (or is a SyncPromise in the rejected state).
  150. * @param {function} onComplete If defined, this calls promise.then to fulfill
  151. * the promise, then calls onComplete(value) with the value of the promise.
  152. * If onComplete is undefined, the return value is described below.
  153. * NOTE: The library will log any exceptions thrown by this callback, but for
  154. * better error handling the callback should catch and properly handle any
  155. * exceptions.
  156. * @param {function} onError (optional) If defined, then onComplete must be
  157. * defined and if there is an error when this calls promise.then, this calls
  158. * onError(err) with the value of the error. If onComplete is undefined, then
  159. * onError is ignored and this will call SyncPromise.getValue(promise) which may
  160. * throw an exception.
  161. * NOTE: The library will log any exceptions thrown by this callback, but for
  162. * better error handling the callback should catch and properly handle any
  163. * exceptions.
  164. * @param {Promise|SyncPromise} promise If onComplete is defined, this calls
  165. * promise.then. Otherwise, this calls SyncPromise.getValue(promise).
  166. * @return {any} If onComplete is undefined, return SyncPromise.getValue(promise).
  167. * Otherwise, if onComplete is supplied then return undefined and use
  168. * onComplete as described above.
  169. * @throws Error If onComplete is undefined and promise is not a SyncPromise.
  170. * @throws any If onComplete is undefined and promise is a SyncPromise in the
  171. * rejected state.
  172. */
  173. SyncPromise.complete = function(onComplete, onErrorOrPromise, promise)
  174. {
  175. var onError;
  176. if (promise)
  177. onError = onErrorOrPromise;
  178. else {
  179. promise = onErrorOrPromise;
  180. onError = null;
  181. }
  182. if (onComplete)
  183. promise
  184. .then(function(value) {
  185. try {
  186. onComplete(value);
  187. } catch (ex) {
  188. console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex));
  189. }
  190. }, function(err) {
  191. if (onError) {
  192. try {
  193. onError(err);
  194. } catch (ex) {
  195. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  196. }
  197. }
  198. else {
  199. if (promise instanceof SyncPromise)
  200. throw err;
  201. else
  202. // We are in an async promise callback, so a thrown exception won't
  203. // reach the caller. Just log it.
  204. console.log("Uncaught exception from a Promise: " +
  205. NdnCommon.getErrorWithStackTrace(err));
  206. }
  207. });
  208. else
  209. return SyncPromise.getValue(promise);
  210. };