promise-toolbox
Version:
Essential utils for promises
1,107 lines (834 loc) • 28.7 kB
Markdown
# promise-toolbox
[](https://npmjs.org/package/promise-toolbox) [](https://travis-ci.org/JsCommunity/promise-toolbox) [](https://packagephobia.now.sh/result?p=promise-toolbox) [](https://github.com/JsCommunity/promise-toolbox/commits/master)
> Essential utils for promises.
**Features:**
- compatible with all promise implementations
- small (< 150 KB with all dependencies, < 5 KB with gzip)
- nice with ES2015 / ES2016 syntax
**Table of contents:**
- [Cancelation](#cancelation)
- [Creation](#creation)
- [Consumption](#consumption)
- [Registering async handlers](#registering-async-handlers)
- [Is cancel token?](#is-cancel-token)
- [Compatibility with AbortSignal](#compatibility-with-abortsignal)
- [@cancelable decorator](#cancelable-decorator)
- [Resource management](#resource-management)
- [Creation](#creation-1)
- [Combination](#combination)
- [Consumption](#consumption-1)
- [Functions](#functions)
- [asyncFn(generator)](#asyncfngenerator)
- [asyncFn.cancelable(generator, [getCancelToken])](#asyncfncancelablegenerator-getcanceltoken)
- [defer()](#defer)
- [fromCallback(fn, arg1, ..., argn)](#fromcallbackfn-arg1--argn)
- [fromEvent(emitter, event, [options]) => Promise](#fromeventemitter-event-options--promise)
- [fromEvents(emitter, successEvents, errorEvents) => Promise](#fromeventsemitter-successevents-errorevents--promise)
- [isPromise(value)](#ispromisevalue)
- [nodeify(fn)](#nodeifyfn)
- [pipe(fns)](#pipefns)
- [pipe(value, ...fns)](#pipevalue-fns)
- [promisify(fn, [ context ]) / promisifyAll(obj)](#promisifyfn--context---promisifyallobj)
- [retry(fn, options, [args])](#retryfn-options-args)
- [try(fn)](#tryfn)
- [wrapApply(fn, args, [thisArg]) / wrapCall(fn, arg, [thisArg])](#wrapapplyfn-args-thisarg--wrapcallfn-arg-thisarg)
- [Pseudo-methods](#pseudo-methods)
- [promise::asCallback(cb)](#promiseascallbackcb)
- [promise::catch(predicate, cb)](#promisecatchpredicate-cb)
- [promise::delay(ms, [value])](#promisedelayms-value)
- [collection::forEach(cb)](#collectionforeachcb)
- [promise::ignoreErrors()](#promiseignoreerrors)
- [promise::finally(cb)](#promisefinallycb)
- [promise::reflect()](#promisereflect)
- [promises::some(count)](#promisessomecount)
- [promise::suppressUnhandledRejections()](#promisesuppressunhandledrejections)
- [promise::tap(onResolved, onRejected)](#promisetaponresolved-onrejected)
- [promise::tapCatch(onRejected)](#promisetapcatchonrejected)
- [promise::timeout(ms, [cb])](#promisetimeoutms-cb-or-rejectionvalue)
### Node & [Browserify](http://browserify.org/)/[Webpack](https://webpack.js.org/)
Installation of the [npm package](https://npmjs.org/package/promise-toolbox):
```
> npm install --save promise-toolbox
```
### Browser
You can directly use the build provided at [unpkg.com](https://unpkg.com):
```html
<script src="https://unpkg.com/promise-toolbox@0.8/dist/umd.js"></script>
```
## Usage
### Promise support
If your environment may not natively support promises, you should use a polyfill such as [native-promise-only](https://github.com/getify/native-promise-only).
On Node, if you want to use a specific promise implementation,
[Bluebird](http://bluebirdjs.com/docs/why-bluebird.html) for instance
to have better performance, you can override the global Promise
variable:
```js
global.Promise = require("bluebird");
```
> Note that it should only be done at the application level, never in
> a library!
### Imports
You can either import all the tools directly:
```js
import * as PT from "promise-toolbox";
console.log(PT.isPromise(value));
```
Or import individual tools from the main module:
```js
import { isPromise } from "promise-toolbox";
console.log(isPromise(value));
```
Each tool is also exported with a `p` prefix to work around reserved keywords
and to help differentiate with other tools (like `lodash.map`):
```js
import { pCatch, pMap } from "promise-toolbox";
```
If you are bundling your application (Browserify, Rollup, Webpack, etc.), you
can cherry-pick the tools directly:
```js
import isPromise from "promise-toolbox/isPromise";
import pCatch from "promise-toolbox/catch";
```
## API
### Cancelation
This library provides an implementation of `CancelToken` from the
[cancelable promises specification](https://tc39.github.io/proposal-cancelable-promises/).
A cancel token is an object which can be passed to asynchronous
functions to represent cancelation state.
```js
import { CancelToken } from "promise-toolbox";
```
#### Creation
A cancel token is created by the initiator of the async work and its
cancelation state may be requested at any time.
```js
// Create a token which requests cancelation when a button is clicked.
const token = new CancelToken((cancel) => {
$("#some-button").on("click", () => cancel("button clicked"));
});
```
```js
const { cancel, token } = CancelToken.source();
```
A list of existing tokens can be passed to `source()` to make the created token
follow their cancelation:
```js
// `source.token` will be canceled (synchronously) as soon as `token1` or
// `token2` or token3` is, with the same reason.
const { cancel, token } = CancelToken.source([token1, token2, token3]);
```
#### Consumption
The receiver of the token (the function doing the async work) can:
1. synchronously check whether cancelation has been requested
2. synchronously throw if cancelation has been requested
3. register a callback that will be executed if cancelation is requested
4. pass the token to subtasks
```js
// 1.
if (token.reason) {
console.log("cancelation has been requested", token.reason.message);
}
// 2.
try {
token.throwIfRequested();
} catch (reason) {
console.log("cancelation has been requested", reason.message);
}
// 3.
token.promise.then((reason) => {
console.log("cancelation has been requested", reason.message);
});
// 4.
subtask(token);
```
See [`asyncFn.cancelable`](#asyncfncancelablegenerator) for an easy way to create async functions with built-in cancelation support.
#### Registering async handlers
> Asynchronous handlers are executed on token cancelation and the
> promise returned by the `cancel` function will wait for all handlers
> to settle.
```js
function httpRequest(cancelToken, opts) {
const req = http.request(opts);
req.end();
cancelToken.addHandler(() => {
req.abort();
// waits for the socket to really close for the cancelation to be
// complete
return fromEvent(req, "close");
});
return fromEvent(req, "response");
}
const { cancel, token } = CancelToken.source();
httpRequest(token, {
hostname: "example.org",
}).then((response) => {
// do something with the response of the request
});
// wraps with Promise.resolve() because cancel only returns a promise
// if a handler has returned a promise
Promise.resolve(cancel()).then(() => {
// the request has been properly canceled
});
```
#### Is cancel token?
```js
if (CancelToken.isCancelToken(value)) {
console.log("value is a cancel token");
}
```
#### @cancelable decorator
> **This is deprecated, instead explicitely pass a cancel token or an abort signal:**
```js
const asyncFunction = async (a, b, { cancelToken = CancelToken.none } = {}) => {
cancelToken.promise.then(() => {
// do stuff regarding the cancelation request.
});
// do other stuff.
};
```
> Make your async functions cancelable.
If the first argument passed to the cancelable function is not a
cancel token, a new one is created and injected and the returned
promise will have a `cancel()` method.
```js
import { cancelable, CancelToken } from "promise-toolbox";
const asyncFunction = cancelable(async ($cancelToken, a, b) => {
$cancelToken.promise.then(() => {
// do stuff regarding the cancelation request.
});
// do other stuff.
});
// Either a cancel token is passed:
const source = CancelToken.source();
const promise1 = asyncFunction(source.token, "foo", "bar");
source.cancel("reason");
// Or the returned promise will have a cancel() method:
const promise2 = asyncFunction("foo", "bar");
promise2.cancel("reason");
```
If the function is a method of a class or an object, you can use
`cancelable` as a decorator:
```js
class MyClass {
@cancelable
async asyncMethod($cancelToken, a, b) {
// ...
}
}
```
#### Compatibility with AbortSignal
A cancel token can be created from an abort signal:
```js
const token = CancelToken.from(abortSignal);
```
> If `abortSignal` is already a `CancelToken`, it will be returned directly, making it a breeze to create code accepting both :-)
A cancel token is API compatible with an abort signal and can be used as such:
```js
const { cancel, token } = CancelToken.source();
await fetch(url, { signal: token });
```
### Resource management
> See [Bluebird documentation](http://bluebirdjs.com/docs/api/resource-management.html) for a good explanation.
#### Creation
A disposable is a simple object, which contains a dispose method and possibily a value:
```js
const disposable = { dispose: () => db.close(), value: db };
```
The dispose method may be asynchronous and return a promise.
As a convenience, you can use the `Disposable` class:
```js
import { Disposable } from "promise-toolbox";
const disposable = new Disposable(() => db.close(), db);
```
If the process is more complicated, maybe because this disposable depends on
other disposables, you can use a generator function alongside the
`Disposable.factory` decorator:
```js
const getTable = Disposable.factory(async function* () {
// simply yield a disposable to use it
const db = yield getDb();
const table = await db.getTable();
try {
// yield the value to expose it
yield table;
} finally {
// this is where you can dispose of the resource
await table.close();
}
});
```
#### Combination
Independent disposables can be acquired and disposed in parallel, to achieve
this, you can use `Disposable.all`:
```js
const combined = await Disposable.all([disposable1, disposable2]);
```
Similarly to `Promise.all`, the value of such a disposable, is an array whose
values are the values of the disposables combined.
#### Consumption
To ensure all resources are properly disposed of, disposables must never be
used manually, but via the `Disposable.use` function:
```js
import { Disposable } from "promise-toolbox";
await Disposable.use(
// Don't await the promise here, resource acquisition should be handled by
// `Disposable.use` otherwise, in case of failure, other resources may failed
// to be disposed of.
getTable(),
// If the function can throw synchronously, a wrapper function can be passed
// directly to `Disposable.use`.
() => getTable(),
async (table1, table2) => {
// do something with table1 and table 2
//
// both `table1` and `table2` are guaranteed to be deallocated by the time
// the promise returned by `Disposable.use` is settled
}
);
```
For more complex use cases, just like `Disposable.factory`, the handler can be
a generator function when no disposables are passed:
```js
await Disposable.use(async function* () {
const table1 = yield getTable();
const table2 = yield getTable();
// do something with table1 and table 2
//
// both `table1` and `table2` are guaranteed to be deallocated by the time
// the promise returned by `Disposable.use` is settled
});
```
To enable an entire function to use disposables, you can use `Disposable.wrap`:
```js
// context (`this`) and all arguments are forwarded from the call
const disposableUser = Disposable.wrap(async function* (arg1, arg2) {
const table = yield getDisposable(arg1, arg2);
// do something with table
});
// similar to
function disposableUser(arg1, arg2) {
return Disposable.use(async function* (arg1, arg2) {
const table = yield getDisposable(arg1, arg2);
// do something with table
});
}
```
### Functions
#### asyncFn(generator)
> Create an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) from [a generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)
>
> Similar to [`Bluebird.coroutine`](http://bluebirdjs.com/docs/api/promise.coroutine.html).
```js
import { asyncFn } from 'promise-toolbox'
const getUserName = asyncFn(function * (db, userId)) {
const user = yield db.getRecord(userId)
return user.name
})
```
#### asyncFn.cancelable(generator, [getCancelToken])
> Like [`asyncFn(generator)`](#asyncfngenerator) but the created async function supports [cancelation](#cancelation).
>
> Similar to [CAF](https://github.com/getify/CAF).
```js
import { asyncFn, CancelToken } from 'promise-toolbox'
const getUserName = asyncFn.cancelable(function * (cancelToken, db, userId)) {
// this yield will throw if the cancelToken is activated
const user = yield db.getRecord(userId)
return user.name
})
const source = CancelToken.source()
getUserName(source.token, db, userId).then(
name => {
console.log('user name is', name)
},
error => {
console.error(error)
}
)
// only wait 5 seconds to fetch the user from the database
setTimeout(source.cancel, 5e3)
```
```js
const cancelableAsyncFunction = asyncFn.cancelable(function* (
cancelToken,
...args
) {
// await otherAsyncFunction() but will throw if cancelToken is activated
yield otherAsyncFunction();
// if aborting on cancelation is unwanted (eg because the called function
// already handles cancelation), wrap the promise in an array
yield [otherAsyncFunction(cancelToken)];
// cancelation, just like any rejection, can be catch
try {
yield otherAsyncFunction();
} catch (error) {
if (CancelToken.isCancelToken(error)) {
// do some clean-up here
// the rest of the function has been added as an async handler of the
// CancelToken which will make `cancel` waits for it
}
throw error;
}
return result;
});
```
If the cancel token is not the first param of the decorated function, a getter should be passed to `asyncFn.cancelable`, it's called with the same context and arguments as the decorated function and returns the cancel token:
```js
const cancelableAsyncFunction = asyncFn.cancelable(
function*(arg1, arg2, options) {
// function logic
},
(_arg1, _arg2, { cancelToken = CancelToken.none } = {}) => cancelToken;
);
```
#### defer()
> Discouraged but sometimes necessary way to create a promise.
```js
import { defer } from "promise-toolbox";
const { promise, resolve } = defer();
promise.then((value) => {
console.log(value);
});
resolve(3);
```
#### fromCallback(fn, arg1, ..., argn)
> Easiest and most efficient way to promisify a function call.
```js
import { fromCallback } from "promise-toolbox";
// callback is appended to the list of arguments passed to the function
fromCallback(fs.readFile, "foo.txt").then((content) => {
console.log(content);
});
// if the callback does not go at the end, you can wrap the call
fromCallback((cb) => foo("bar", cb, "baz")).then(() => {
// ...
});
// you can use `.call` to specify the context of execution
fromCallback.call(thisArg, fn, ...args).then(() => {
// ...
});
// finally, if you want to call a method, you can pass its name instead of a
// function
fromCallback.call(object, "method", ...args).then(() => {
// ...
});
```
#### fromEvent(emitter, event, [options]) => Promise
> Wait for one event. The first parameter of the emitted event is used
> to resolve/reject the promise.
```js
const promise = fromEvent(emitter, "foo", {
// whether the promise resolves to an array of all the event args
// instead of simply the first arg
array: false,
// whether the error event can reject the promise
ignoreErrors: false,
// name of the error event
error: "error",
});
promise.then(
(value) => {
console.log("foo event was emitted with value", value);
},
(reason) => {
console.error("an error has been emitted", reason);
}
);
```
#### fromEvents(emitter, successEvents, errorEvents) => Promise
> Wait for one of multiple events. The array of all the parameters of
> the emitted event is used to resolve/reject the promise.
>
> The array also has an `event` property indicating which event has
> been emitted.
```js
fromEvents(emitter, ["foo", "bar"], ["error1", "error2"]).then(
(event) => {
console.log(
"event %s have been emitted with values",
event.name,
event.args
);
},
(reasons) => {
console.error(
"error event %s has been emitted with errors",
event.names,
event.args
);
}
);
```
#### isPromise(value)
```js
import { isPromise } from "promise-toolbox";
if (isPromise(foo())) {
console.log("foo() returns a promise");
}
```
#### nodeify(fn)
> From async functions return promises, create new ones taking node-style
> callbacks.
```js
import { nodeify } = require('promise-toolbox')
const writable = new Writable({
write: nodeify(async function (chunk, encoding) {
// ...
})
})
```
#### pipe(fns)
> Create a new function from the composition of async functions.
```js
import { pipe } from "promise-toolbox";
const getUserPreferences = pipe(getUser, getPreferences);
```
#### pipe(value, ...fns)
> Makes value flow through a list of async functions.
```js
import { pipe } from "promise-toolbox";
const output = await pipe(
input, // plain value or promise
transform1, // sync or async function
transform2,
transform3
);
```
#### promisify(fn, [ context ]) / promisifyAll(obj)
> From async functions taking node-style callbacks, create new ones
> returning promises.
```js
import fs from "fs";
import { promisify, promisifyAll } from "promise-toolbox";
// Promisify a single function.
//
// If possible, the function name is kept and the new length is set.
const readFile = promisify(fs.readFile);
// Or all functions (own or inherited) exposed on a object.
const fsPromise = promisifyAll(fs);
readFile(__filename).then((content) => console.log(content));
fsPromise.readFile(__filename).then((content) => console.log(content));
```
#### retry(fn, options, [args])
> Retries an async function when it fails.
```js
import { retry } from "promise-toolbox";
(async () => {
await retry(
async () => {
const response = await fetch("https://pokeapi.co/api/v2/pokemon/3/");
if (response.status === 500) {
// no need to retry in this case
throw retry.bail(new Error(response.statusText));
}
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.json();
},
{
// predicate when to retry, default on always but programmer errors
// (ReferenceError, SyntaxError and TypeError)
//
// similar to `promise-toolbox/catch`, it can be a constructor, an object,
// a function, or an array of the previous
when: { message: "my error message" },
// this function is called before a retry is scheduled (before the delay)
async onRetry(error) {
console.warn("attempt", this.attemptNumber, "failed with error", error);
console.warn("next try in", this.delay, "milliseconds");
// Other information available:
// - this.fn: function that failed
// - this.arguments: arguments passed to fn
// - this.this: context passed to fn
// This function can throw to prevent any retry.
// The retry delay will start only after this function has finished.
},
// delay before a retry, default to 1000 ms
delay: 2000,
// number of tries including the first one, default to 10
//
// cannot be used with `retries`
tries: 3,
// number of retries (excluding the initial run), default to undefined
//
// cannot be used with `tries`
retries: 4,
// instead of passing `delay`, `tries` and `retries`, you can pass an
// iterable of delays to use to retry
//
// in this example, it will retry 3 times, first after 1 second, then
// after 2 seconds and one last time after 4 seconds
//
// for more advanced uses, see https://github.com/JsCommunity/iterable-backoff
delays: [1e3, 2e3, 4e3],
}
);
})().catch(console.error.bind(console));
```
The most efficient way to make a function automatically retry is to wrap it:
```js
MyClass.prototype.myMethod = retry.wrap(MyClass.prototype.myMethod, {
delay: 1e3,
retries: 10,
when: MyError,
});
```
In that case `options` can also be a function which will be used to compute the options from the context and the arguments:
```js
MyClass.prototype.myMethod = retry.wrap(
MyClass.prototype.myMethod,
function getOptions(arg1, arg2) {
return this._computeRetryOptions(arg1, arg2);
}
);
```
#### try(fn)
> Starts a chain of promises.
```js
import PromiseToolbox from "promise-toolbox";
const getUserById = (id) =>
PromiseToolbox.try(() => {
if (typeof id !== "number") {
throw new Error("id must be a number");
}
return db.getUserById(id);
});
```
> Note: similar to `Promise.resolve().then(fn)` but calls `fn()`
> synchronously.
#### wrapApply(fn, args, [thisArg]) / wrapCall(fn, arg, [thisArg])
> Wrap a call to a function to always return a promise.
```js
function getUserById(id) {
if (typeof id !== "number") {
throw new TypeError("id must be a number");
}
return db.getUser(id);
}
wrapCall(getUserById, "foo").catch((error) => {
// id must be a number
});
```
### Pseudo-methods
This function can be used as if they were methods, i.e. by passing the
promise (or promises) as the context.
This is extremely easy using [ES2016's bind syntax](https://github.com/zenparsing/es-function-bind).
```js
const promises = [Promise.resolve("foo"), Promise.resolve("bar")];
promises::all().then((values) => {
console.log(values);
});
// → [ 'foo', 'bar' ]
```
If you are still an older version of ECMAScript, fear not: simply pass
the promise (or promises) as the first argument of the `.call()`
method:
```js
const promises = [Promise.resolve("foo"), Promise.resolve("bar")];
all.call(promises).then(function (values) {
console.log(values);
});
// → [ 'foo', 'bar' ]
```
#### promise::asCallback(cb)
> Register a node-style callback on this promise.
```js
import { asCallback } from "promise-toolbox";
// This function can be used either with node-style callbacks or with
// promises.
function getDataFor(input, callback) {
return dataFromDataBase(input)::asCallback(callback);
}
```
#### promise::catch(predicate, cb)
> Similar to `Promise#catch()` but:
>
> - support predicates
> - do not catch `ReferenceError`, `SyntaxError` or `TypeError` unless
> they match a predicate because they are usually programmer errors
> and should be handled separately.
```js
somePromise
.then(() => {
return a.b.c.d();
})
::pCatch(TypeError, ReferenceError, (reason) => {
// Will end up here on programmer error
})
::pCatch(NetworkError, TimeoutError, (reason) => {
// Will end up here on expected everyday network errors
})
::pCatch((reason) => {
// Catch any unexpected errors
});
```
#### promise::delay(ms, [value])
> Delays the resolution of a promise by `ms` milliseconds.
>
> Note: the rejection is not delayed.
```js
console.log(await Promise.resolve("500ms passed")::delay(500));
// → 500 ms passed
```
Also works with a value:
```js
console.log(await delay(500, "500ms passed"));
// → 500 ms passed
```
Like `setTimeout` in Node, it is possible to
[`unref`](https://nodejs.org/dist/latest-v11.x/docs/api/timers.html#timers_timeout_unref)
the timer:
```js
await delay(500).unref();
```
#### collection::forEach(cb)
> Iterates in order over a collection, or promise of collection, which
> contains a mix of promises and values, waiting for each call of cb
> to be resolved before the next one.
The returned promise will resolve to `undefined` when the iteration is
complete.
```js
["foo", Promise.resolve("bar")]::forEach((value) => {
console.log(value);
// Wait for the promise to be resolve before the next item.
return new Promise((resolve) => setTimeout(resolve, 10));
});
// →
// foo
// bar
```
#### promise::ignoreErrors()
> Ignore (operational) errors for this promise.
```js
import { ignoreErrors } from "promise-toolbox";
// will not emit an unhandled rejection error if the file does not
// exist
readFileAsync("foo.txt")
.then((content) => {
console.log(content);
})
::ignoreErrors();
// will emit an unhandled rejection error due to the typo
readFileAsync("foo.txt")
.then((content) => {
console.lgo(content); // typo
})
::ignoreErrors();
```
#### promise::finally(cb)
> Execute a handler regardless of the promise fate. Similar to the
> `finally` block in synchronous codes.
>
> The resolution value or rejection reason of the initial promise is
> forwarded unless the callback rejects.
```js
import { pFinally } from "promise-toolbox";
function ajaxGetAsync(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
})::pFinally(() => {
$("#ajax-loader-animation").hide();
});
}
```
#### promise::reflect()
> Returns a promise which resolves to an objects which reflects the
> resolution of this promise.
```js
import { reflect } from "promise-toolbox";
const inspection = await promise::reflect();
if (inspection.isFulfilled()) {
console.log(inspection.value());
} else {
console.error(inspection.reason());
}
```
#### promises::some(count)
> Waits for `count` promises in a collection to be resolved.
```js
import { some } from "promise-toolbox";
const [first, seconds] = await [
ping("ns1.example.org"),
ping("ns2.example.org"),
ping("ns3.example.org"),
ping("ns4.example.org"),
]::some(2);
```
#### promise::suppressUnhandledRejections()
> Suppress unhandled rejections, needed when error handlers are attached
> asynchronously after the promise has rejected.
>
> Similar to [`Bluebird#suppressUnhandledRejections()`](http://bluebirdjs.com/docs/api/suppressunhandledrejections.html).
```js
const promise = getUser()::suppressUnhandledRejections();
$(document).on("ready", () => {
promise.catch((error) => {
console.error("error while getting user", error);
});
});
```
#### promise::tap(onResolved, onRejected)
> Like `.then()` but the original resolution/rejection is forwarded.
>
> Like `::finally()`, if the callback rejects, it takes over the
> original resolution/rejection.
```js
import { tap } from "promise-toolbox";
// Contrary to .then(), using ::tap() does not change the resolution
// value.
const promise1 = Promise.resolve(42)::tap((value) => {
console.log(value);
});
// Like .then, the second param is used in case of rejection.
const promise2 = Promise.reject(42)::tap(null, (reason) => {
console.error(reason);
});
```
#### promise::tapCatch(onRejected)
> Alias to [`promise:tap(null, onRejected)`](#promisetaponresolved-onrejected).
#### promise::timeout(ms, [cb or rejectionValue])
> Call a callback if the promise is still pending after `ms`
> milliseconds. Its resolution/rejection is forwarded.
>
> If the callback is omitted, the returned promise is rejected with a
> `TimeoutError`.
```js
import { timeout, TimeoutError } from "promise-toolbox";
await doLongOperation()::timeout(100, () => {
return doFallbackOperation();
});
await doLongOperation()::timeout(100);
await doLongOperation()::timeout(
100,
new Error("the long operation has failed")
);
```
> Note: `0` is a special value which disable the timeout, useful if the delay is
> configurable in your app.
## Development
```
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production
> npm run build
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/JsCommunity/promise-toolbox/issues)
you've encountered;
- fork and create a pull request.
## License
ISC © [Julien Fontanet](https://github.com/julien-f)