util.promise

Promises allow a non-blocking/asynchronous function to return a “promise” of a future value, before it is actually available.

An alternative (and common) solution is for such functions to have a ‘callback’ parameter - calling this callback function with the data when it becomes available.

Promises are more flexible than callbacks, and can be much simpler to use when multiple asynchronous operations are involved (e.g. either nested or in sequence). Although promises are a generic concept, this module is based heavily on the Promise API available in Javascript/ES6, which is already proven in many applications and APIs and may already be familiar to many people.

Usage

Using a function that returns a promise

If an API is documented as returning a promise object, be aware that just like with callback-based APIs, the function will return immediately. To execute code when the operation is complete, you can pass a callback to the ‘next()’ method of the promise object:

promise:next() automatically returns another promise based on the return value of the callback, which means calls to :next() can be chained:

If the original operation fails, or if one of the callbacks fails, subsequent promises in the chain will go into an error state, and their normal callbacks will not be called. You can handle these errors by supplying an error handling function either as the second parameter of :next(), or to a :catch() method call.

Writing a function that returns a promise

Promises are easy to use with any existing async API. This function demonstrates how an API that takes a callback could be trivially converted into one that returns a promise:

Functions

promise.new(func)

Creates a new promise and immediately executes the function func, passing it two parameters: on_fulfilled and on_error.

The function should call on_fulfilled(data) (where data is optional and can be any value) when it wants to resolve the promise.

If there is a problem and the promise cannot be fulfilled, calling on_error(err) will execute any error callbacks that have been set on the promise, passing err which again can be any value. Any unhandled errors in func will automatically result in on_error() being called with the uncaught error object.

Example:

promise.resolve(value)

A helper function that returns a promise that is already fulfilled with the given value. That is, if :next(callback) is called on this promise, it would execute callback immediately with the provided value.

promise.reject(err)

Similar to promise.resolve(), except returns a promise that is in an error state. That is, if :next(ok_callback, err_callback) is called, err_callback would immediately be called with the value of err.

promise.race(array_of_promises)

Returns a new promise that fulfills when any of the promises in the passed array are fulfilled, and it fulfills with that value, or errors when any of the promises in the array error. Subsequent changes in any promises are ignored.

Use this function to wait for the first of multiple asynchronous operations. For example, to add a timeout to an asynchronous operation you might write code like the following:

promise.all(array_of_promises)

Returns a new promise that only fulfills when all of the promises in the provided array are fulfilled. It fulfills with a new array contains the values returned by each of the promises (in the same order as the input array).

Use this function when you want to wait for multiple asynchronous operations to complete successfully, for example:

promise.try(func)

Executes the provided function, and:

  • Converts any non-promise return value to a fulfilled promise with that value
  • Converts any uncaught errors to a failed promise with the uncaught error object
  • Returns a promise bound to the result of a promise returned by func, if it returns a promise

This function is useful for wrapping non-promise code, and acting as a promise-friendly alternative to pcall(), when error-safety is required inside a function that is supposed to always return errors via a promise.

Methods

promise:next(on_fulfilled, on_error)

Set callbacks to be executed when the promise is fulfilled or an error occurs. Only one of these callbacks will be executed, and only once (per promise they are attached to).

This method can be called multiple times on the same promise… callbacks will execute in the order they were attached.

It also returns a new promise which is automatically fulfilled with the return value of the on_fulfilled callback. This allows :next() calls to be chained. If the callback returns a promise object, the result of that returned promise is used to resolve the promise returned from :next() - i.e. the result will be passed to later callbacks in the chain.

Both callbacks are optional - you can provide either without the other.

promise:catch(on_error)

A neater way to set just an error handler for the promise. Identical behaviour to promise:next(nil, on_error).

promise:finally(on_finally)

Adds a callback that will run in both success and failure of the promise.

Note that, unlike on_fulfilled and on_error callbacks:

  • on_finally does not receive the result of the promise (in either the success or failure case).
  • Although :finally() does return a promise (and thus allows chaining like :next() and :catch()), it does not use the return value of the on_finally callback, but the result of the original promise.
  • An exception to the above is in the case of an uncaught error inside on_finally - in this case the promise returned by :finally() will be in an error state and on_fulfilled callbacks later in the chain will not be called.

This method is useful to add some cleanup of resources once the promise is settled one way or another.