mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
860 lines
22 KiB
860 lines
22 KiB
|
|
/**
|
|
* An object representing a "promise" for a future value
|
|
*
|
|
* @param {?function(T, ?)=} onSuccess a function to handle successful
|
|
* resolution of this promise
|
|
* @param {?function(!Error, ?)=} onFail a function to handle failed
|
|
* resolution of this promise
|
|
* @constructor
|
|
* @template T
|
|
*/
|
|
function Promise(onSuccess, onFail) {
|
|
this.promise = this
|
|
this._isPromise = true
|
|
this._successFn = onSuccess
|
|
this._failFn = onFail
|
|
this._scope = this
|
|
this._boundArgs = null
|
|
this._hasContext = false
|
|
this._nextContext = undefined
|
|
this._currentContext = undefined
|
|
}
|
|
|
|
/**
|
|
* @param {function()} callback
|
|
*/
|
|
function nextTick (callback) {
|
|
callback()
|
|
}
|
|
|
|
if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
|
nextTick = process.nextTick
|
|
}
|
|
|
|
/**
|
|
* All callback execution should go through this function. While the
|
|
* implementation below is simple, it can be replaced with more sophisticated
|
|
* implementations that enforce QoS on the event loop.
|
|
*
|
|
* @param {Promise} defer
|
|
* @param {Function} callback
|
|
* @param {Object|undefined} scope
|
|
* @param {Array} args
|
|
*/
|
|
function nextTickCallback (defer, callback, scope, args) {
|
|
try {
|
|
defer.resolve(callback.apply(scope, args))
|
|
} catch (thrown) {
|
|
defer.reject(thrown)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used for accessing the nextTick function from outside the kew module.
|
|
*
|
|
* @return {Function}
|
|
*/
|
|
function getNextTickFunction () {
|
|
return nextTick
|
|
}
|
|
|
|
/**
|
|
* Used for overriding the nextTick function from outside the kew module so that
|
|
* the user can plug and play lower level schedulers
|
|
* @param {!Function} fn
|
|
*/
|
|
function setNextTickFunction (fn) {
|
|
nextTick = fn
|
|
}
|
|
|
|
/**
|
|
* Keep track of the number of promises that are rejected along side
|
|
* the number of rejected promises we call _failFn on so we can look
|
|
* for leaked rejections.
|
|
* @constructor
|
|
*/
|
|
function PromiseStats() {
|
|
/** @type {number} */
|
|
this.errorsEmitted = 0
|
|
|
|
/** @type {number} */
|
|
this.errorsHandled = 0
|
|
}
|
|
|
|
var stats = new PromiseStats()
|
|
|
|
Promise.prototype._handleError = function () {
|
|
if (!this._errorHandled) {
|
|
stats.errorsHandled++
|
|
this._errorHandled = true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specify that the current promise should have a specified context
|
|
* @param {*} context context
|
|
* @private
|
|
*/
|
|
Promise.prototype._useContext = function (context) {
|
|
this._nextContext = this._currentContext = context
|
|
this._hasContext = true
|
|
return this
|
|
}
|
|
|
|
Promise.prototype.clearContext = function () {
|
|
this._hasContext = false
|
|
this._nextContext = undefined
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Set the context for all promise handlers to follow
|
|
*
|
|
* NOTE(dpup): This should be considered deprecated. It does not do what most
|
|
* people would expect. The context will be passed as a second argument to all
|
|
* subsequent callbacks.
|
|
*
|
|
* @param {*} context An arbitrary context
|
|
*/
|
|
Promise.prototype.setContext = function (context) {
|
|
this._nextContext = context
|
|
this._hasContext = true
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Get the context for a promise
|
|
* @return {*} the context set by setContext
|
|
*/
|
|
Promise.prototype.getContext = function () {
|
|
return this._nextContext
|
|
}
|
|
|
|
/**
|
|
* Resolve this promise with a specified value
|
|
*
|
|
* @param {*=} data
|
|
*/
|
|
Promise.prototype.resolve = function (data) {
|
|
if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")
|
|
|
|
var i
|
|
if (data && isPromise(data)) {
|
|
this._child = data
|
|
if (this._promises) {
|
|
for (i = 0; i < this._promises.length; i += 1) {
|
|
data._chainPromise(this._promises[i])
|
|
}
|
|
delete this._promises
|
|
}
|
|
|
|
if (this._onComplete) {
|
|
for (i = 0; i < this._onComplete.length; i+= 1) {
|
|
data.fin(this._onComplete[i])
|
|
}
|
|
delete this._onComplete
|
|
}
|
|
} else if (data && isPromiseLike(data)) {
|
|
data.then(
|
|
function(data) { this.resolve(data) }.bind(this),
|
|
function(err) { this.reject(err) }.bind(this)
|
|
)
|
|
} else {
|
|
this._hasData = true
|
|
this._data = data
|
|
|
|
if (this._onComplete) {
|
|
for (i = 0; i < this._onComplete.length; i++) {
|
|
this._onComplete[i]()
|
|
}
|
|
}
|
|
|
|
if (this._promises) {
|
|
for (i = 0; i < this._promises.length; i += 1) {
|
|
this._promises[i]._useContext(this._nextContext)
|
|
this._promises[i]._withInput(data)
|
|
}
|
|
delete this._promises
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reject this promise with an error
|
|
*
|
|
* @param {!Error} e
|
|
*/
|
|
Promise.prototype.reject = function (e) {
|
|
if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice")
|
|
|
|
var i
|
|
this._error = e
|
|
stats.errorsEmitted++
|
|
|
|
if (this._ended) {
|
|
this._handleError()
|
|
process.nextTick(function onPromiseThrow() {
|
|
throw e
|
|
})
|
|
}
|
|
|
|
if (this._onComplete) {
|
|
for (i = 0; i < this._onComplete.length; i++) {
|
|
this._onComplete[i]()
|
|
}
|
|
}
|
|
|
|
if (this._promises) {
|
|
this._handleError()
|
|
for (i = 0; i < this._promises.length; i += 1) {
|
|
this._promises[i]._useContext(this._nextContext)
|
|
this._promises[i]._withError(e)
|
|
}
|
|
delete this._promises
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provide a callback to be called whenever this promise successfully
|
|
* resolves. Allows for an optional second callback to handle the failure
|
|
* case.
|
|
*
|
|
* @param {?function(this:void, T, ?): RESULT|undefined} onSuccess
|
|
* @param {?function(this:void, !Error, ?): RESULT=} onFail
|
|
* @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess or
|
|
* onFail handler
|
|
* @template RESULT
|
|
*/
|
|
Promise.prototype.then = function (onSuccess, onFail) {
|
|
var promise = new Promise(onSuccess, onFail)
|
|
if (this._nextContext) promise._useContext(this._nextContext)
|
|
|
|
if (this._child) this._child._chainPromise(promise)
|
|
else this._chainPromise(promise)
|
|
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Provide a callback to be called whenever this promise successfully
|
|
* resolves. The callback will be executed in the context of the provided scope.
|
|
*
|
|
* @param {function(this:SCOPE, ...): RESULT} onSuccess
|
|
* @param {SCOPE} scope Object whose context callback will be executed in.
|
|
* @param {...*} var_args Additional arguments to be passed to the promise callback.
|
|
* @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess
|
|
* @template SCOPE, RESULT
|
|
*/
|
|
Promise.prototype.thenBound = function (onSuccess, scope, var_args) {
|
|
var promise = new Promise(onSuccess)
|
|
if (this._nextContext) promise._useContext(this._nextContext)
|
|
|
|
promise._scope = scope
|
|
if (arguments.length > 2) {
|
|
promise._boundArgs = Array.prototype.slice.call(arguments, 2)
|
|
}
|
|
|
|
// Chaining must happen after setting args and scope since it may fire callback.
|
|
if (this._child) this._child._chainPromise(promise)
|
|
else this._chainPromise(promise)
|
|
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Provide a callback to be called whenever this promise is rejected
|
|
*
|
|
* @param {function(this:void, !Error, ?)} onFail
|
|
* @return {!Promise.<T>} returns a new promise with the output of the onFail handler
|
|
*/
|
|
Promise.prototype.fail = function (onFail) {
|
|
return this.then(null, onFail)
|
|
}
|
|
|
|
/**
|
|
* Provide a callback to be called whenever this promise is rejected.
|
|
* The callback will be executed in the context of the provided scope.
|
|
*
|
|
* @param {function(this:SCOPE, ...)} onFail
|
|
* @param {SCOPE} scope Object whose context callback will be executed in.
|
|
* @param {...?} var_args
|
|
* @return {!Promise.<T>} returns a new promise with the output of the onSuccess
|
|
* @template SCOPE
|
|
*/
|
|
Promise.prototype.failBound = function (onFail, scope, var_args) {
|
|
var promise = new Promise(null, onFail)
|
|
if (this._nextContext) promise._useContext(this._nextContext)
|
|
|
|
promise._scope = scope
|
|
if (arguments.length > 2) {
|
|
promise._boundArgs = Array.prototype.slice.call(arguments, 2)
|
|
}
|
|
|
|
// Chaining must happen after setting args and scope since it may fire callback.
|
|
if (this._child) this._child._chainPromise(promise)
|
|
else this._chainPromise(promise)
|
|
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Spread a promises outputs to the functions arguments.
|
|
* @param {?function(this:void, ...): RESULT|undefined} onSuccess
|
|
* @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess or
|
|
* onFail handler
|
|
* @template RESULT
|
|
*/
|
|
Promise.prototype.spread = function (onSuccess) {
|
|
return this.then(allInternal)
|
|
.then(function (array) {
|
|
return onSuccess.apply(null, array)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Spread a promises outputs to the functions arguments.
|
|
* @param {function(this:SCOPE, ...): RESULT} onSuccess
|
|
* @param {SCOPE} scope Object whose context callback will be executed in.
|
|
* @param {...*} var_args Additional arguments to be passed to the promise callback.
|
|
* @return {!Promise.<RESULT>} returns a new promise with the output of the onSuccess
|
|
* @template SCOPE, RESULT
|
|
*/
|
|
Promise.prototype.spreadBound = function (onSuccess, scope, var_args) {
|
|
var args = Array.prototype.slice.call(arguments, 2)
|
|
return this.then(allInternal)
|
|
.then(function (array) {
|
|
return onSuccess.apply(scope, args.concat(array))
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Provide a callback to be called whenever this promise is either resolved
|
|
* or rejected.
|
|
*
|
|
* @param {function()} onComplete
|
|
* @return {!Promise.<T>} returns the current promise
|
|
*/
|
|
Promise.prototype.fin = function (onComplete) {
|
|
if (this._hasData || this._error) {
|
|
onComplete()
|
|
return this
|
|
}
|
|
|
|
if (this._child) {
|
|
this._child.fin(onComplete)
|
|
} else {
|
|
if (!this._onComplete) this._onComplete = [onComplete]
|
|
else this._onComplete.push(onComplete)
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Mark this promise as "ended". If the promise is rejected, this will throw an
|
|
* error in whatever scope it happens to be in
|
|
*
|
|
* @return {!Promise.<T>} returns the current promise
|
|
* @deprecated Prefer done(), because it's consistent with Q.
|
|
*/
|
|
Promise.prototype.end = function () {
|
|
this._end()
|
|
return this
|
|
}
|
|
|
|
|
|
/**
|
|
* Mark this promise as "ended".
|
|
* @private
|
|
*/
|
|
Promise.prototype._end = function () {
|
|
if (this._error) {
|
|
this._handleError()
|
|
throw this._error
|
|
}
|
|
this._ended = true
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Close the promise. Any errors after this completes will be thrown to the global handler.
|
|
*
|
|
* @param {?function(this:void, T, ?)=} onSuccess a function to handle successful
|
|
* resolution of this promise
|
|
* @param {?function(this:void, !Error, ?)=} onFailure a function to handle failed
|
|
* resolution of this promise
|
|
* @return {void}
|
|
*/
|
|
Promise.prototype.done = function (onSuccess, onFailure) {
|
|
var self = this
|
|
if (onSuccess || onFailure) {
|
|
self = self.then(onSuccess, onFailure)
|
|
}
|
|
self._end()
|
|
}
|
|
|
|
/**
|
|
* Return a new promise that behaves the same as the current promise except
|
|
* that it will be rejected if the current promise does not get fulfilled
|
|
* after a certain amount of time.
|
|
*
|
|
* @param {number} timeoutMs The timeout threshold in msec
|
|
* @param {string=} timeoutMsg error message
|
|
* @return {!Promise.<T>} a new promise with timeout
|
|
*/
|
|
Promise.prototype.timeout = function (timeoutMs, timeoutMsg) {
|
|
var deferred = new Promise()
|
|
var isTimeout = false
|
|
|
|
var timeout = setTimeout(function() {
|
|
deferred.reject(new Error(timeoutMsg || 'Promise timeout after ' + timeoutMs + ' ms.'))
|
|
isTimeout = true
|
|
}, timeoutMs)
|
|
|
|
this.then(function (data) {
|
|
if (!isTimeout) {
|
|
clearTimeout(timeout)
|
|
deferred.resolve(data)
|
|
}
|
|
},
|
|
function (err) {
|
|
if (!isTimeout) {
|
|
clearTimeout(timeout)
|
|
deferred.reject(err)
|
|
}
|
|
})
|
|
|
|
return deferred.promise
|
|
}
|
|
|
|
/**
|
|
* Attempt to resolve this promise with the specified input
|
|
*
|
|
* @param {*} data the input
|
|
*/
|
|
Promise.prototype._withInput = function (data) {
|
|
if (this._successFn) {
|
|
this._nextTick(this._successFn, [data, this._currentContext])
|
|
} else {
|
|
this.resolve(data)
|
|
}
|
|
|
|
// context is no longer needed
|
|
delete this._currentContext
|
|
}
|
|
|
|
/**
|
|
* Attempt to reject this promise with the specified error
|
|
*
|
|
* @param {!Error} e
|
|
* @private
|
|
*/
|
|
Promise.prototype._withError = function (e) {
|
|
if (this._failFn) {
|
|
this._nextTick(this._failFn, [e, this._currentContext])
|
|
} else {
|
|
this.reject(e)
|
|
}
|
|
|
|
// context is no longer needed
|
|
delete this._currentContext
|
|
}
|
|
|
|
/**
|
|
* Calls a function in the correct scope, and includes bound arguments.
|
|
* @param {Function} fn
|
|
* @param {Array} args
|
|
* @private
|
|
*/
|
|
Promise.prototype._nextTick = function (fn, args) {
|
|
if (this._boundArgs) {
|
|
args = this._boundArgs.concat(args)
|
|
}
|
|
nextTick(nextTickCallback.bind(null, this, fn, this._scope, args))
|
|
}
|
|
|
|
/**
|
|
* Chain a promise to the current promise
|
|
*
|
|
* @param {!Promise} promise the promise to chain
|
|
* @private
|
|
*/
|
|
Promise.prototype._chainPromise = function (promise) {
|
|
var i
|
|
if (this._hasContext) promise._useContext(this._nextContext)
|
|
|
|
if (this._child) {
|
|
this._child._chainPromise(promise)
|
|
} else if (this._hasData) {
|
|
promise._withInput(this._data)
|
|
} else if (this._error) {
|
|
// We can't rely on _withError() because it's called on the chained promises
|
|
// and we need to use the source's _errorHandled state
|
|
this._handleError()
|
|
promise._withError(this._error)
|
|
} else if (!this._promises) {
|
|
this._promises = [promise]
|
|
} else {
|
|
this._promises.push(promise)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility function used for creating a node-style resolver
|
|
* for deferreds
|
|
*
|
|
* @param {!Promise} deferred a promise that looks like a deferred
|
|
* @param {Error=} err an optional error
|
|
* @param {*=} data optional data
|
|
*/
|
|
function resolver(deferred, err, data) {
|
|
if (err) deferred.reject(err)
|
|
else deferred.resolve(data)
|
|
}
|
|
|
|
/**
|
|
* Creates a node-style resolver for a deferred by wrapping
|
|
* resolver()
|
|
*
|
|
* @return {function(?Error, *)} node-style callback
|
|
*/
|
|
Promise.prototype.makeNodeResolver = function () {
|
|
return resolver.bind(null, this)
|
|
}
|
|
|
|
/**
|
|
* Return true iff the given object is a promise of this library.
|
|
*
|
|
* Because kew's API is slightly different than other promise libraries,
|
|
* it's important that we have a test for its promise type. If you want
|
|
* to test for a more general A+ promise, you should do a cap test for
|
|
* the features you want.
|
|
*
|
|
* @param {*} obj The object to test
|
|
* @return {boolean} Whether the object is a promise
|
|
*/
|
|
function isPromise(obj) {
|
|
return !!obj._isPromise
|
|
}
|
|
|
|
/**
|
|
* Return true iff the given object is a promise-like object, e.g. appears to
|
|
* implement Promises/A+ specification
|
|
*
|
|
* @param {*} obj The object to test
|
|
* @return {boolean} Whether the object is a promise-like object
|
|
*/
|
|
function isPromiseLike(obj) {
|
|
return (typeof obj === 'object' || typeof obj === 'function') &&
|
|
typeof obj.then === 'function'
|
|
}
|
|
|
|
/**
|
|
* Static function which creates and resolves a promise immediately
|
|
*
|
|
* @param {T} data data to resolve the promise with
|
|
* @return {!Promise.<T>}
|
|
* @template T
|
|
*/
|
|
function resolve(data) {
|
|
var promise = new Promise()
|
|
promise.resolve(data)
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Static function which creates and rejects a promise immediately
|
|
*
|
|
* @param {!Error} e error to reject the promise with
|
|
* @return {!Promise}
|
|
*/
|
|
function reject(e) {
|
|
var promise = new Promise()
|
|
promise.reject(e)
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Replace an element in an array with a new value. Used by .all() to
|
|
* call from .then()
|
|
*
|
|
* @param {!Array} arr
|
|
* @param {number} idx
|
|
* @param {*} val
|
|
* @return {*} the val that's being injected into the array
|
|
*/
|
|
function replaceEl(arr, idx, val) {
|
|
arr[idx] = val
|
|
return val
|
|
}
|
|
|
|
/**
|
|
* Replace an element in an array as it is resolved with its value.
|
|
* Used by .allSettled().
|
|
*
|
|
* @param {!Array} arr
|
|
* @param {number} idx
|
|
* @param {*} value The value from a resolved promise.
|
|
* @return {*} the data that's being passed in
|
|
*/
|
|
function replaceElFulfilled(arr, idx, value) {
|
|
arr[idx] = {
|
|
state: 'fulfilled',
|
|
value: value
|
|
}
|
|
return value
|
|
}
|
|
|
|
/**
|
|
* Replace an element in an array as it is rejected with the reason.
|
|
* Used by .allSettled().
|
|
*
|
|
* @param {!Array} arr
|
|
* @param {number} idx
|
|
* @param {*} reason The reason why the original promise is rejected
|
|
* @return {*} the data that's being passed in
|
|
*/
|
|
function replaceElRejected(arr, idx, reason) {
|
|
arr[idx] = {
|
|
state: 'rejected',
|
|
reason: reason
|
|
}
|
|
return reason
|
|
}
|
|
|
|
/**
|
|
* Takes in an array of promises or literals and returns a promise which returns
|
|
* an array of values when all have resolved. If any fail, the promise fails.
|
|
*
|
|
* @param {!Array.<!Promise>} promises
|
|
* @return {!Promise.<!Array>}
|
|
*/
|
|
function all(promises) {
|
|
if (arguments.length != 1 || !Array.isArray(promises)) {
|
|
promises = Array.prototype.slice.call(arguments, 0)
|
|
}
|
|
return allInternal(promises)
|
|
}
|
|
|
|
/**
|
|
* A version of all() that does not accept var_args
|
|
*
|
|
* @param {!Array.<!Promise>} promises
|
|
* @return {!Promise.<!Array>}
|
|
*/
|
|
function allInternal(promises) {
|
|
if (!promises.length) return resolve([])
|
|
|
|
var outputs = []
|
|
var finished = false
|
|
var promise = new Promise()
|
|
var counter = promises.length
|
|
|
|
for (var i = 0; i < promises.length; i += 1) {
|
|
if (!promises[i] || !isPromiseLike(promises[i])) {
|
|
outputs[i] = promises[i]
|
|
counter -= 1
|
|
} else {
|
|
promises[i].then(replaceEl.bind(null, outputs, i))
|
|
.then(function decrementAllCounter() {
|
|
counter--
|
|
if (!finished && counter === 0) {
|
|
finished = true
|
|
promise.resolve(outputs)
|
|
}
|
|
}, function onAllError(e) {
|
|
if (!finished) {
|
|
finished = true
|
|
promise.reject(e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
if (counter === 0 && !finished) {
|
|
finished = true
|
|
promise.resolve(outputs)
|
|
}
|
|
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Takes in an array of promises or values and returns a promise that is
|
|
* fulfilled with an array of state objects when all have resolved or
|
|
* rejected. If a promise is resolved, its corresponding state object is
|
|
* {state: 'fulfilled', value: Object}; whereas if a promise is rejected, its
|
|
* corresponding state object is {state: 'rejected', reason: Object}.
|
|
*
|
|
* @param {!Array} promises or values
|
|
* @return {!Promise.<!Array>} Promise fulfilled with state objects for each input
|
|
*/
|
|
function allSettled(promises) {
|
|
if (!Array.isArray(promises)) {
|
|
throw Error('The input to "allSettled()" should be an array of Promise or values')
|
|
}
|
|
if (!promises.length) return resolve([])
|
|
|
|
var outputs = []
|
|
var promise = new Promise()
|
|
var counter = promises.length
|
|
|
|
for (var i = 0; i < promises.length; i += 1) {
|
|
if (!promises[i] || !isPromiseLike(promises[i])) {
|
|
replaceElFulfilled(outputs, i, promises[i])
|
|
if ((--counter) === 0) promise.resolve(outputs)
|
|
} else {
|
|
promises[i]
|
|
.then(replaceElFulfilled.bind(null, outputs, i), replaceElRejected.bind(null, outputs, i))
|
|
.then(function () {
|
|
if ((--counter) === 0) promise.resolve(outputs)
|
|
})
|
|
}
|
|
}
|
|
|
|
return promise
|
|
}
|
|
|
|
/**
|
|
* Takes an array of results and spreads them to the arguments of a function.
|
|
* @param {!Array} array
|
|
* @param {!Function} fn
|
|
*/
|
|
function spread(array, fn) {
|
|
resolve(array).spread(fn)
|
|
}
|
|
|
|
/**
|
|
* Create a new Promise which looks like a deferred
|
|
*
|
|
* @return {!Promise}
|
|
*/
|
|
function defer() {
|
|
return new Promise()
|
|
}
|
|
|
|
/**
|
|
* Return a promise which will wait a specified number of ms to resolve
|
|
*
|
|
* @param {*} delayMsOrVal A delay (in ms) if this takes one argument, or ther
|
|
* return value if it takes two.
|
|
* @param {number=} opt_delayMs
|
|
* @return {!Promise}
|
|
*/
|
|
function delay(delayMsOrVal, opt_delayMs) {
|
|
var returnVal = undefined
|
|
var delayMs = delayMsOrVal
|
|
if (typeof opt_delayMs != 'undefined') {
|
|
delayMs = opt_delayMs
|
|
returnVal = delayMsOrVal
|
|
}
|
|
|
|
if (typeof delayMs != 'number') {
|
|
throw new Error('Bad delay value ' + delayMs)
|
|
}
|
|
|
|
var defer = new Promise()
|
|
setTimeout(function onDelay() {
|
|
defer.resolve(returnVal)
|
|
}, delayMs)
|
|
return defer
|
|
}
|
|
|
|
/**
|
|
* Returns a promise that has the same result as `this`, but fulfilled
|
|
* after at least ms milliseconds
|
|
* @param {number} ms
|
|
*/
|
|
Promise.prototype.delay = function (ms) {
|
|
return this.then(function (val) {
|
|
return delay(val, ms)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Return a promise which will evaluate the function fn in a future turn with
|
|
* the provided args
|
|
*
|
|
* @param {function(...)} fn
|
|
* @param {...*} var_args a variable number of arguments
|
|
* @return {!Promise}
|
|
*/
|
|
function fcall(fn, var_args) {
|
|
var rootArgs = Array.prototype.slice.call(arguments, 1)
|
|
var defer = new Promise()
|
|
nextTick(nextTickCallback.bind(null, defer, fn, undefined, rootArgs))
|
|
return defer
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a promise that will be invoked with the result of a node style
|
|
* callback. All args to fn should be given except for the final callback arg
|
|
*
|
|
* @param {function(...)} fn
|
|
* @param {...*} var_args a variable number of arguments
|
|
* @return {!Promise}
|
|
*/
|
|
function nfcall(fn, var_args) {
|
|
// Insert an undefined argument for scope and let bindPromise() do the work.
|
|
var args = Array.prototype.slice.call(arguments, 0)
|
|
args.splice(1, 0, undefined)
|
|
return ncall.apply(undefined, args)
|
|
}
|
|
|
|
|
|
/**
|
|
* Like `nfcall`, but permits passing a `this` context for the call.
|
|
*
|
|
* @param {function(...)} fn
|
|
* @param {Object} scope
|
|
* @param {...*} var_args
|
|
* @return {!Promise}
|
|
*/
|
|
function ncall(fn, scope, var_args) {
|
|
return bindPromise.apply(null, arguments)()
|
|
}
|
|
|
|
|
|
/**
|
|
* Binds a function to a scope with an optional number of curried arguments. Attaches
|
|
* a node style callback as the last argument and returns a promise
|
|
*
|
|
* @param {function(...)} fn
|
|
* @param {Object} scope
|
|
* @param {...*} var_args a variable number of arguments
|
|
* @return {function(...)}: !Promise}
|
|
*/
|
|
function bindPromise(fn, scope, var_args) {
|
|
var rootArgs = Array.prototype.slice.call(arguments, 2)
|
|
return function onBoundPromise(var_args) {
|
|
var defer = new Promise()
|
|
try {
|
|
fn.apply(scope, rootArgs.concat(Array.prototype.slice.call(arguments, 0), defer.makeNodeResolver()))
|
|
} catch (e) {
|
|
defer.reject(e)
|
|
}
|
|
return defer
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
all: all,
|
|
bindPromise: bindPromise,
|
|
defer: defer,
|
|
delay: delay,
|
|
fcall: fcall,
|
|
isPromise: isPromise,
|
|
isPromiseLike: isPromiseLike,
|
|
ncall: ncall,
|
|
nfcall: nfcall,
|
|
resolve: resolve,
|
|
reject: reject,
|
|
spread: spread,
|
|
stats: stats,
|
|
allSettled: allSettled,
|
|
Promise: Promise,
|
|
getNextTickFunction: getNextTickFunction,
|
|
setNextTickFunction: setNextTickFunction,
|
|
}
|
|
|