kew.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. /**
  2. * An object representing a "promise" for a future value
  3. *
  4. * @param {?function(T, ?)=} onSuccess a function to handle successful
  5. * resolution of this promise
  6. * @param {?function(!Error, ?)=} onFail a function to handle failed
  7. * resolution of this promise
  8. * @constructor
  9. * @template T
  10. */
  11. function Promise(onSuccess, onFail) {
  12. this.promise = this
  13. this._isPromise = true
  14. this._successFn = onSuccess
  15. this._failFn = onFail
  16. this._scope = this
  17. this._boundArgs = null
  18. this._hasContext = false
  19. this._nextContext = undefined
  20. this._currentContext = undefined
  21. }
  22. /**
  23. * @param {function()} callback
  24. */
  25. function nextTick (callback) {
  26. callback()
  27. }
  28. if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
  29. nextTick = process.nextTick
  30. }
  31. /**
  32. * All callback execution should go through this function. While the
  33. * implementation below is simple, it can be replaced with more sophisticated
  34. * implementations that enforce QoS on the event loop.
  35. *
  36. * @param {Promise} defer
  37. * @param {Function} callback
  38. * @param {Object|undefined} scope
  39. * @param {Array} args
  40. */
  41. function nextTickCallback (defer, callback, scope, args) {
  42. try {
  43. defer.resolve(callback.apply(scope, args))
  44. } catch (thrown) {
  45. defer.reject(thrown)
  46. }
  47. }
  48. /**
  49. * Used for accessing the nextTick function from outside the kew module.
  50. *
  51. * @return {Function}
  52. */
  53. function getNextTickFunction () {
  54. return nextTick
  55. }
  56. /**
  57. * Used for overriding the nextTick function from outside the kew module so that
  58. * the user can plug and play lower level schedulers
  59. * @param {!Function} fn
  60. */
  61. function setNextTickFunction (fn) {
  62. nextTick = fn
  63. }
  64. /**
  65. * Keep track of the number of promises that are rejected along side
  66. * the number of rejected promises we call _failFn on so we can look
  67. * for leaked rejections.
  68. * @constructor
  69. */
  70. function PromiseStats() {
  71. /** @type {number} */
  72. this.errorsEmitted = 0
  73. /** @type {number} */
  74. this.errorsHandled = 0
  75. }
  76. var stats = new PromiseStats()
  77. Promise.prototype._handleError = function () {
  78. if (!this._errorHandled) {
  79. stats.errorsHandled++
  80. this._errorHandled = true
  81. }
  82. }
  83. /**
  84. * Specify that the current promise should have a specified context
  85. * @param {*} context context
  86. * @private
  87. */
  88. Promise.prototype._useContext = function (context) {
  89. this._nextContext = this._currentContext = context
  90. this._hasContext = true
  91. return this
  92. }
  93. Promise.prototype.clearContext = function () {
  94. this._hasContext = false
  95. this._nextContext = undefined
  96. return this
  97. }
  98. /**
  99. * Set the context for all promise handlers to follow
  100. *
  101. * NOTE(dpup): This should be considered deprecated. It does not do what most
  102. * people would expect. The context will be passed as a second argument to all
  103. * subsequent callbacks.
  104. *
  105. * @param {*} context An arbitrary context
  106. */
  107. Promise.prototype.setContext = function (context) {
  108. this._nextContext = context
  109. this._hasContext = true
  110. return this
  111. }
  112. /**
  113. * Get the context for a promise
  114. * @return {*} the context set by setContext
  115. */
  116. Promise.prototype.getContext = function () {
  117. return this._nextContext
  118. }
  119. /**
  120. * Resolve this promise with a specified value
  121. *
  122. * @param {*=} data
  123. */
  124. Promise.prototype.resolve = function (data) {
  125. if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")
  126. var i
  127. if (data && isPromise(data)) {
  128. this._child = data
  129. if (this._promises) {
  130. for (i = 0; i < this._promises.length; i += 1) {
  131. data._chainPromise(this._promises[i])
  132. }
  133. delete this._promises
  134. }
  135. if (this._onComplete) {
  136. for (i = 0; i < this._onComplete.length; i+= 1) {
  137. data.fin(this._onComplete[i])
  138. }
  139. delete this._onComplete
  140. }
  141. } else if (data && isPromiseLike(data)) {
  142. data.then(
  143. function(data) { this.resolve(data) }.bind(this),
  144. function(err) { this.reject(err) }.bind(this)
  145. )
  146. } else {
  147. this._hasData = true
  148. this._data = data
  149. if (this._onComplete) {
  150. for (i = 0; i < this._onComplete.length; i++) {
  151. this._onComplete[i]()
  152. }
  153. }
  154. if (this._promises) {
  155. for (i = 0; i < this._promises.length; i += 1) {
  156. this._promises[i]._useContext(this._nextContext)
  157. this._promises[i]._withInput(data)
  158. }
  159. delete this._promises
  160. }
  161. }
  162. }
  163. /**
  164. * Reject this promise with an error
  165. *
  166. * @param {!Error} e
  167. */
  168. Promise.prototype.reject = function (e) {
  169. if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")
  170. var i
  171. this._error = e
  172. stats.errorsEmitted++
  173. if (this._ended) {
  174. this._handleError()
  175. process.nextTick(function onPromiseThrow() {
  176. throw e
  177. })
  178. }
  179. if (this._onComplete) {
  180. for (i = 0; i < this._onComplete.length; i++) {
  181. this._onComplete[i]()
  182. }
  183. }
  184. if (this._promises) {
  185. this._handleError()
  186. for (i = 0; i < this._promises.length; i += 1) {
  187. this._promises[i]._useContext(this._nextContext)
  188. this._promises[i]._withError(e)
  189. }
  190. delete this._promises
  191. }
  192. }
  193. /**
  194. * Provide a callback to be called whenever this promise successfully
  195. * resolves. Allows for an optional second callback to handle the failure
  196. * case.
  197. *
  198. * @param {?function(this:void, T, ?): RESULT|undefined} onSuccess
  199. * @param {?function(this:void, !Error, ?): RESULT=} onFail
  200. * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess or
  201. * onFail handler
  202. * @template RESULT
  203. */
  204. Promise.prototype.then = function (onSuccess, onFail) {
  205. var promise = new Promise(onSuccess, onFail)
  206. if (this._nextContext) promise._useContext(this._nextContext)
  207. if (this._child) this._child._chainPromise(promise)
  208. else this._chainPromise(promise)
  209. return promise
  210. }
  211. /**
  212. * Provide a callback to be called whenever this promise successfully
  213. * resolves. The callback will be executed in the context of the provided scope.
  214. *
  215. * @param {function(this:SCOPE, ...): RESULT} onSuccess
  216. * @param {SCOPE} scope Object whose context callback will be executed in.
  217. * @param {...*} var_args Additional arguments to be passed to the promise callback.
  218. * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess
  219. * @template SCOPE, RESULT
  220. */
  221. Promise.prototype.thenBound = function (onSuccess, scope, var_args) {
  222. var promise = new Promise(onSuccess)
  223. if (this._nextContext) promise._useContext(this._nextContext)
  224. promise._scope = scope
  225. if (arguments.length > 2) {
  226. promise._boundArgs = Array.prototype.slice.call(arguments, 2)
  227. }
  228. // Chaining must happen after setting args and scope since it may fire callback.
  229. if (this._child) this._child._chainPromise(promise)
  230. else this._chainPromise(promise)
  231. return promise
  232. }
  233. /**
  234. * Provide a callback to be called whenever this promise is rejected
  235. *
  236. * @param {function(this:void, !Error, ?)} onFail
  237. * @return {!Promise.<T>} returns a new promise with the output of the onFail handler
  238. */
  239. Promise.prototype.fail = function (onFail) {
  240. return this.then(null, onFail)
  241. }
  242. /**
  243. * Provide a callback to be called whenever this promise is rejected.
  244. * The callback will be executed in the context of the provided scope.
  245. *
  246. * @param {function(this:SCOPE, ...)} onFail
  247. * @param {SCOPE} scope Object whose context callback will be executed in.
  248. * @param {...?} var_args
  249. * @return {!Promise.<T>} returns a new promise with the output of the onSuccess
  250. * @template SCOPE
  251. */
  252. Promise.prototype.failBound = function (onFail, scope, var_args) {
  253. var promise = new Promise(null, onFail)
  254. if (this._nextContext) promise._useContext(this._nextContext)
  255. promise._scope = scope
  256. if (arguments.length > 2) {
  257. promise._boundArgs = Array.prototype.slice.call(arguments, 2)
  258. }
  259. // Chaining must happen after setting args and scope since it may fire callback.
  260. if (this._child) this._child._chainPromise(promise)
  261. else this._chainPromise(promise)
  262. return promise
  263. }
  264. /**
  265. * Spread a promises outputs to the functions arguments.
  266. * @param {?function(this:void, ...): RESULT|undefined} onSuccess
  267. * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess or
  268. * onFail handler
  269. * @template RESULT
  270. */
  271. Promise.prototype.spread = function (onSuccess) {
  272. return this.then(allInternal)
  273. .then(function (array) {
  274. return onSuccess.apply(null, array)
  275. })
  276. }
  277. /**
  278. * Spread a promises outputs to the functions arguments.
  279. * @param {function(this:SCOPE, ...): RESULT} onSuccess
  280. * @param {SCOPE} scope Object whose context callback will be executed in.
  281. * @param {...*} var_args Additional arguments to be passed to the promise callback.
  282. * @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess
  283. * @template SCOPE, RESULT
  284. */
  285. Promise.prototype.spreadBound = function (onSuccess, scope, var_args) {
  286. var args = Array.prototype.slice.call(arguments, 2)
  287. return this.then(allInternal)
  288. .then(function (array) {
  289. return onSuccess.apply(scope, args.concat(array))
  290. })
  291. }
  292. /**
  293. * Provide a callback to be called whenever this promise is either resolved
  294. * or rejected.
  295. *
  296. * @param {function()} onComplete
  297. * @return {!Promise.<T>} returns the current promise
  298. */
  299. Promise.prototype.fin = function (onComplete) {
  300. if (this._hasData || this._error) {
  301. onComplete()
  302. return this
  303. }
  304. if (this._child) {
  305. this._child.fin(onComplete)
  306. } else {
  307. if (!this._onComplete) this._onComplete = [onComplete]
  308. else this._onComplete.push(onComplete)
  309. }
  310. return this
  311. }
  312. /**
  313. * Mark this promise as "ended". If the promise is rejected, this will throw an
  314. * error in whatever scope it happens to be in
  315. *
  316. * @return {!Promise.<T>} returns the current promise
  317. * @deprecated Prefer done(), because it's consistent with Q.
  318. */
  319. Promise.prototype.end = function () {
  320. this._end()
  321. return this
  322. }
  323. /**
  324. * Mark this promise as "ended".
  325. * @private
  326. */
  327. Promise.prototype._end = function () {
  328. if (this._error) {
  329. this._handleError()
  330. throw this._error
  331. }
  332. this._ended = true
  333. return this
  334. }
  335. /**
  336. * Close the promise. Any errors after this completes will be thrown to the global handler.
  337. *
  338. * @param {?function(this:void, T, ?)=} onSuccess a function to handle successful
  339. * resolution of this promise
  340. * @param {?function(this:void, !Error, ?)=} onFailure a function to handle failed
  341. * resolution of this promise
  342. * @return {void}
  343. */
  344. Promise.prototype.done = function (onSuccess, onFailure) {
  345. var self = this
  346. if (onSuccess || onFailure) {
  347. self = self.then(onSuccess, onFailure)
  348. }
  349. self._end()
  350. }
  351. /**
  352. * Return a new promise that behaves the same as the current promise except
  353. * that it will be rejected if the current promise does not get fulfilled
  354. * after a certain amount of time.
  355. *
  356. * @param {number} timeoutMs The timeout threshold in msec
  357. * @param {string=} timeoutMsg error message
  358. * @return {!Promise.<T>} a new promise with timeout
  359. */
  360. Promise.prototype.timeout = function (timeoutMs, timeoutMsg) {
  361. var deferred = new Promise()
  362. var isTimeout = false
  363. var timeout = setTimeout(function() {
  364. deferred.reject(new Error(timeoutMsg || 'Promise timeout after ' + timeoutMs + ' ms.'))
  365. isTimeout = true
  366. }, timeoutMs)
  367. this.then(function (data) {
  368. if (!isTimeout) {
  369. clearTimeout(timeout)
  370. deferred.resolve(data)
  371. }
  372. },
  373. function (err) {
  374. if (!isTimeout) {
  375. clearTimeout(timeout)
  376. deferred.reject(err)
  377. }
  378. })
  379. return deferred.promise
  380. }
  381. /**
  382. * Attempt to resolve this promise with the specified input
  383. *
  384. * @param {*} data the input
  385. */
  386. Promise.prototype._withInput = function (data) {
  387. if (this._successFn) {
  388. this._nextTick(this._successFn, [data, this._currentContext])
  389. } else {
  390. this.resolve(data)
  391. }
  392. // context is no longer needed
  393. delete this._currentContext
  394. }
  395. /**
  396. * Attempt to reject this promise with the specified error
  397. *
  398. * @param {!Error} e
  399. * @private
  400. */
  401. Promise.prototype._withError = function (e) {
  402. if (this._failFn) {
  403. this._nextTick(this._failFn, [e, this._currentContext])
  404. } else {
  405. this.reject(e)
  406. }
  407. // context is no longer needed
  408. delete this._currentContext
  409. }
  410. /**
  411. * Calls a function in the correct scope, and includes bound arguments.
  412. * @param {Function} fn
  413. * @param {Array} args
  414. * @private
  415. */
  416. Promise.prototype._nextTick = function (fn, args) {
  417. if (this._boundArgs) {
  418. args = this._boundArgs.concat(args)
  419. }
  420. nextTick(nextTickCallback.bind(null, this, fn, this._scope, args))
  421. }
  422. /**
  423. * Chain a promise to the current promise
  424. *
  425. * @param {!Promise} promise the promise to chain
  426. * @private
  427. */
  428. Promise.prototype._chainPromise = function (promise) {
  429. var i
  430. if (this._hasContext) promise._useContext(this._nextContext)
  431. if (this._child) {
  432. this._child._chainPromise(promise)
  433. } else if (this._hasData) {
  434. promise._withInput(this._data)
  435. } else if (this._error) {
  436. // We can't rely on _withError() because it's called on the chained promises
  437. // and we need to use the source's _errorHandled state
  438. this._handleError()
  439. promise._withError(this._error)
  440. } else if (!this._promises) {
  441. this._promises = [promise]
  442. } else {
  443. this._promises.push(promise)
  444. }
  445. }
  446. /**
  447. * Utility function used for creating a node-style resolver
  448. * for deferreds
  449. *
  450. * @param {!Promise} deferred a promise that looks like a deferred
  451. * @param {Error=} err an optional error
  452. * @param {*=} data optional data
  453. */
  454. function resolver(deferred, err, data) {
  455. if (err) deferred.reject(err)
  456. else deferred.resolve(data)
  457. }
  458. /**
  459. * Creates a node-style resolver for a deferred by wrapping
  460. * resolver()
  461. *
  462. * @return {function(?Error, *)} node-style callback
  463. */
  464. Promise.prototype.makeNodeResolver = function () {
  465. return resolver.bind(null, this)
  466. }
  467. /**
  468. * Return true iff the given object is a promise of this library.
  469. *
  470. * Because kew's API is slightly different than other promise libraries,
  471. * it's important that we have a test for its promise type. If you want
  472. * to test for a more general A+ promise, you should do a cap test for
  473. * the features you want.
  474. *
  475. * @param {*} obj The object to test
  476. * @return {boolean} Whether the object is a promise
  477. */
  478. function isPromise(obj) {
  479. return !!obj._isPromise
  480. }
  481. /**
  482. * Return true iff the given object is a promise-like object, e.g. appears to
  483. * implement Promises/A+ specification
  484. *
  485. * @param {*} obj The object to test
  486. * @return {boolean} Whether the object is a promise-like object
  487. */
  488. function isPromiseLike(obj) {
  489. return (typeof obj === 'object' || typeof obj === 'function') &&
  490. typeof obj.then === 'function'
  491. }
  492. /**
  493. * Static function which creates and resolves a promise immediately
  494. *
  495. * @param {T} data data to resolve the promise with
  496. * @return {!Promise.<T>}
  497. * @template T
  498. */
  499. function resolve(data) {
  500. var promise = new Promise()
  501. promise.resolve(data)
  502. return promise
  503. }
  504. /**
  505. * Static function which creates and rejects a promise immediately
  506. *
  507. * @param {!Error} e error to reject the promise with
  508. * @return {!Promise}
  509. */
  510. function reject(e) {
  511. var promise = new Promise()
  512. promise.reject(e)
  513. return promise
  514. }
  515. /**
  516. * Replace an element in an array with a new value. Used by .all() to
  517. * call from .then()
  518. *
  519. * @param {!Array} arr
  520. * @param {number} idx
  521. * @param {*} val
  522. * @return {*} the val that's being injected into the array
  523. */
  524. function replaceEl(arr, idx, val) {
  525. arr[idx] = val
  526. return val
  527. }
  528. /**
  529. * Replace an element in an array as it is resolved with its value.
  530. * Used by .allSettled().
  531. *
  532. * @param {!Array} arr
  533. * @param {number} idx
  534. * @param {*} value The value from a resolved promise.
  535. * @return {*} the data that's being passed in
  536. */
  537. function replaceElFulfilled(arr, idx, value) {
  538. arr[idx] = {
  539. state: 'fulfilled',
  540. value: value
  541. }
  542. return value
  543. }
  544. /**
  545. * Replace an element in an array as it is rejected with the reason.
  546. * Used by .allSettled().
  547. *
  548. * @param {!Array} arr
  549. * @param {number} idx
  550. * @param {*} reason The reason why the original promise is rejected
  551. * @return {*} the data that's being passed in
  552. */
  553. function replaceElRejected(arr, idx, reason) {
  554. arr[idx] = {
  555. state: 'rejected',
  556. reason: reason
  557. }
  558. return reason
  559. }
  560. /**
  561. * Takes in an array of promises or literals and returns a promise which returns
  562. * an array of values when all have resolved. If any fail, the promise fails.
  563. *
  564. * @param {!Array.<!Promise>} promises
  565. * @return {!Promise.<!Array>}
  566. */
  567. function all(promises) {
  568. if (arguments.length != 1 || !Array.isArray(promises)) {
  569. promises = Array.prototype.slice.call(arguments, 0)
  570. }
  571. return allInternal(promises)
  572. }
  573. /**
  574. * A version of all() that does not accept var_args
  575. *
  576. * @param {!Array.<!Promise>} promises
  577. * @return {!Promise.<!Array>}
  578. */
  579. function allInternal(promises) {
  580. if (!promises.length) return resolve([])
  581. var outputs = []
  582. var finished = false
  583. var promise = new Promise()
  584. var counter = promises.length
  585. for (var i = 0; i < promises.length; i += 1) {
  586. if (!promises[i] || !isPromiseLike(promises[i])) {
  587. outputs[i] = promises[i]
  588. counter -= 1
  589. } else {
  590. promises[i].then(replaceEl.bind(null, outputs, i))
  591. .then(function decrementAllCounter() {
  592. counter--
  593. if (!finished && counter === 0) {
  594. finished = true
  595. promise.resolve(outputs)
  596. }
  597. }, function onAllError(e) {
  598. if (!finished) {
  599. finished = true
  600. promise.reject(e)
  601. }
  602. })
  603. }
  604. }
  605. if (counter === 0 && !finished) {
  606. finished = true
  607. promise.resolve(outputs)
  608. }
  609. return promise
  610. }
  611. /**
  612. * Takes in an array of promises or values and returns a promise that is
  613. * fulfilled with an array of state objects when all have resolved or
  614. * rejected. If a promise is resolved, its corresponding state object is
  615. * {state: 'fulfilled', value: Object}; whereas if a promise is rejected, its
  616. * corresponding state object is {state: 'rejected', reason: Object}.
  617. *
  618. * @param {!Array} promises or values
  619. * @return {!Promise.<!Array>} Promise fulfilled with state objects for each input
  620. */
  621. function allSettled(promises) {
  622. if (!Array.isArray(promises)) {
  623. throw Error('The input to "allSettled()" should be an array of Promise or values')
  624. }
  625. if (!promises.length) return resolve([])
  626. var outputs = []
  627. var promise = new Promise()
  628. var counter = promises.length
  629. for (var i = 0; i < promises.length; i += 1) {
  630. if (!promises[i] || !isPromiseLike(promises[i])) {
  631. replaceElFulfilled(outputs, i, promises[i])
  632. if ((--counter) === 0) promise.resolve(outputs)
  633. } else {
  634. promises[i]
  635. .then(replaceElFulfilled.bind(null, outputs, i), replaceElRejected.bind(null, outputs, i))
  636. .then(function () {
  637. if ((--counter) === 0) promise.resolve(outputs)
  638. })
  639. }
  640. }
  641. return promise
  642. }
  643. /**
  644. * Takes an array of results and spreads them to the arguments of a function.
  645. * @param {!Array} array
  646. * @param {!Function} fn
  647. */
  648. function spread(array, fn) {
  649. resolve(array).spread(fn)
  650. }
  651. /**
  652. * Create a new Promise which looks like a deferred
  653. *
  654. * @return {!Promise}
  655. */
  656. function defer() {
  657. return new Promise()
  658. }
  659. /**
  660. * Return a promise which will wait a specified number of ms to resolve
  661. *
  662. * @param {*} delayMsOrVal A delay (in ms) if this takes one argument, or ther
  663. * return value if it takes two.
  664. * @param {number=} opt_delayMs
  665. * @return {!Promise}
  666. */
  667. function delay(delayMsOrVal, opt_delayMs) {
  668. var returnVal = undefined
  669. var delayMs = delayMsOrVal
  670. if (typeof opt_delayMs != 'undefined') {
  671. delayMs = opt_delayMs
  672. returnVal = delayMsOrVal
  673. }
  674. if (typeof delayMs != 'number') {
  675. throw new Error('Bad delay value ' + delayMs)
  676. }
  677. var defer = new Promise()
  678. setTimeout(function onDelay() {
  679. defer.resolve(returnVal)
  680. }, delayMs)
  681. return defer
  682. }
  683. /**
  684. * Returns a promise that has the same result as `this`, but fulfilled
  685. * after at least ms milliseconds
  686. * @param {number} ms
  687. */
  688. Promise.prototype.delay = function (ms) {
  689. return this.then(function (val) {
  690. return delay(val, ms)
  691. })
  692. }
  693. /**
  694. * Return a promise which will evaluate the function fn in a future turn with
  695. * the provided args
  696. *
  697. * @param {function(...)} fn
  698. * @param {...*} var_args a variable number of arguments
  699. * @return {!Promise}
  700. */
  701. function fcall(fn, var_args) {
  702. var rootArgs = Array.prototype.slice.call(arguments, 1)
  703. var defer = new Promise()
  704. nextTick(nextTickCallback.bind(null, defer, fn, undefined, rootArgs))
  705. return defer
  706. }
  707. /**
  708. * Returns a promise that will be invoked with the result of a node style
  709. * callback. All args to fn should be given except for the final callback arg
  710. *
  711. * @param {function(...)} fn
  712. * @param {...*} var_args a variable number of arguments
  713. * @return {!Promise}
  714. */
  715. function nfcall(fn, var_args) {
  716. // Insert an undefined argument for scope and let bindPromise() do the work.
  717. var args = Array.prototype.slice.call(arguments, 0)
  718. args.splice(1, 0, undefined)
  719. return ncall.apply(undefined, args)
  720. }
  721. /**
  722. * Like `nfcall`, but permits passing a `this` context for the call.
  723. *
  724. * @param {function(...)} fn
  725. * @param {Object} scope
  726. * @param {...*} var_args
  727. * @return {!Promise}
  728. */
  729. function ncall(fn, scope, var_args) {
  730. return bindPromise.apply(null, arguments)()
  731. }
  732. /**
  733. * Binds a function to a scope with an optional number of curried arguments. Attaches
  734. * a node style callback as the last argument and returns a promise
  735. *
  736. * @param {function(...)} fn
  737. * @param {Object} scope
  738. * @param {...*} var_args a variable number of arguments
  739. * @return {function(...)}: !Promise}
  740. */
  741. function bindPromise(fn, scope, var_args) {
  742. var rootArgs = Array.prototype.slice.call(arguments, 2)
  743. return function onBoundPromise(var_args) {
  744. var defer = new Promise()
  745. try {
  746. fn.apply(scope, rootArgs.concat(Array.prototype.slice.call(arguments, 0), defer.makeNodeResolver()))
  747. } catch (e) {
  748. defer.reject(e)
  749. }
  750. return defer
  751. }
  752. }
  753. module.exports = {
  754. all: all,
  755. bindPromise: bindPromise,
  756. defer: defer,
  757. delay: delay,
  758. fcall: fcall,
  759. isPromise: isPromise,
  760. isPromiseLike: isPromiseLike,
  761. ncall: ncall,
  762. nfcall: nfcall,
  763. resolve: resolve,
  764. reject: reject,
  765. spread: spread,
  766. stats: stats,
  767. allSettled: allSettled,
  768. Promise: Promise,
  769. getNextTickFunction: getNextTickFunction,
  770. setNextTickFunction: setNextTickFunction,
  771. }