better-promises

JavaScript's Promise
enhanced, flexible version you may find useful in your project.
Installation
yarn add better-promises
pnpm i better-promises
npm i better-promises
Creating
The BetterPromise
class has several ways of creating its instance.
- Using no arguments at all
import { BetterPromise } from 'better-promises';
const promise = new BetterPromise();
const controller = new AbortController();
const promise = new BetterPromise({
abortSignal: controller.signal,
timeout: 3000
});
- Using the promise executor. In this case the executor will receive an additional
argument representing the execution context (you will learn about it later).
const promise = new BetterPromise((resolve, reject, context) => {
});
- Using both executor and options.
const controller = new AbortController();
const promise = new BetterPromise((resolve, reject, context) => {
}, {
abortSignal: controller.signal,
timeout: 3000,
});
When the promise was resolved or rejected, the executor receives a corresponding event:
new BetterPromise((resolve, reject, context) => {
context.on('rejected', reason => {
});
context.on('resolved', result => {
});
context.on('finalized', result => {
});
});
Now, let's learn more about its executor and execution context.
Execution Context
Executor is a function, passed to the promise constructor. Execution context is an additional
argument passed to the executor. It contains useful data that executor may use.
readonly abortSignal: unknown
Abort signal created by the promise. You can use this in order to know when the promise was either rejected or
resolved. You will likely need this value in order to pass it to some other abortable operations.
new BetterPromise((resolve, reject, { abortSignal }) => {
abortSignal.onabort = () => {
console.log('Promise was finalized:', abortSignal.reason);
};
});
get isRejected(): boolean
Returns true
if the promise was rejected.
new BetterPromise((resolve, reject, context) => {
console.log(context.isRejected);
reject();
console.log(context.isRejected);
});
get isResolved(): boolean
Returns true
if the promise was resolved.
new BetterPromise((resolve, reject, context) => {
console.log(context.isResolved);
resolve();
console.log(context.isResoled);
});
on(event, listener): VoidFunction
Adds an event listener to one of the following events:
resolved
- the payload will be a resolved value;
rejected
- the payload will be a rejection reason;
finalized
- the payload will be one of the following objects:
{ kind: 'resolved', result: T }
{ kind: 'rejected', reason: TimeoutError | CancelledError | unknown }
new BetterPromise((resolve, reject, context) => {
context.on('resolved', result => {
console.log(result);
});
resolve(123);
});
new BetterPromise((resolve, reject, context) => {
context.on('rejected', reason => {
console.log(reason);
});
resolve(new Error('test'))
});
new BetterPromise((resolve, reject, context) => {
context.on('finalized', value => {
console.log(value);
});
resolve(123)
});
Note that if the promise is already in fulfilled state, listeners bound to the finalized
and resolved
events will
be called instantaneously. We can say the same about a rejected promise and trying to listen to the rejected
event.
new BetterPromise((resolve, reject, context) => {
resolve(123);
context.on('resolved', result => {
});
});
get result(): T | undefined
Returns promise resolve result.
new BetterPromise((resolve, reject, context) => {
console.log(context.result);
resolve(123);
console.log(context.result);
});
get rejectReason(): TimeoutError | CancelledError | unknown | undefined
Returns promise rejection reason.
new BetterPromise((resolve, reject, context) => {
console.log(context.rejectReason);
reject(new Error('test'));
console.log(context.rejectReason);
});
throwIfRejected(): void
Will throw an error if the promise is currently reject. The thrown error will be equal to the rejection reason.
const controller = new AbortController();
controller.abort(new Error('Hey ho!'));
new BetterPromise((resolve, reject, context) => {
context.throwIfRejected();
}, {
abortSignal: controller.signal,
})
.catch(e => {
console.log(e);
});
Options
abortSignal?: AbortSignal
An abort signal to let the promise know, it should abort the execution.
const controller = new AbortController();
setTimeout(() => {
controller.abort(new Error('Oops!'));
}, 1000);
const promise = new BetterPromise({ abortSignal: controller.signal })
.catch(e => {
console.log(e);
});
abortOnReject?: boolean = true
Should the abortSignal
passed to the executor be aborted if the promise was rejected.
By default, as long as there is no point to perform any operations at the moment of rejection,
the signal will be aborted.
To prevent the signal from being aborted, use the false
value.
new BetterPromise((resolve, reject, context) => {
context.abortSignal.onabort = () => {
};
reject(new Error('test'));
});
new BetterPromise((resolve, reject, context) => {
context.abortSignal.onabort = () => {
};
reject(new Error('test'));
}, { abortOnReject: false });
abortOnResolve?: boolean = true
Should the abortSignal
passed to the executor be aborted if the promise was fulfilled.
By default, as long as there is no point to perform any operations at the moment of resolve, the signal will be aborted.
To prevent the signal from being aborted, use the false
value.
new BetterPromise((resolve, reject, context) => {
context.abortSignal.onabort = () => {
};
resolve(123)
});
new BetterPromise((resolve, reject, context) => {
context.abortSignal.onabort = () => {
};
resolve(123)
}, { abortOnResolve: false });
To check if the abort reason represents a promise resolve result, use the isResolved
function:
new BetterPromise((resolve, reject, context) => {
context.abortSignal.onabort = () => {
if (isResolved(context.abortSignal.reason)) {
console.log(context.abortSignal.reason.value);
}
};
resolve(123)
});
timeout?: number
Timeout in milliseconds after which the promise will be aborted with the TimeoutError
error.
const promise = new BetterPromise({ timeout: 1000 })
.catch(e => {
if (TimeoutError.is(e)) {
console.log(e);
}
});
Methods
In addition to standard promise methods (then
, catch
, and finally
), BetterPromise
introduces three new methods: abort
, reject
and cancel
. It also provides a static
method fn
.
fn
The fn
static method executes a function and resolves its result.
The executed function receives the same execution context as when using the default way of
using BetterPromise
via constructor.
The method optionally accepts options passed to the BetterPromise
constructor.
const controller = new AbortController();
const promise = BetterPromise.fn(context => 'Resolved!', {
abortSignal: controller.signal,
timeout: 3000,
});
promise.then(console.log);
const promise2 = BetterPromise.fn(() => {
throw new Error('Nah :(');
});
promise2.catch(console.error);
const promise3 = BetterPromise.fn(async () => {
const r = await fetch('...');
return r.json();
});
reject
The reject
method rejects the initially created promise with a given reason. It is important to
note that reject
applies to the original promise, regardless of any chained promises. So, calling
this method, only the initially created promise will be rejected to follow the expected flow.
The expected flow is the flow when rejection was performed in the promise executor (the function,
passed to the promise constructor), and then all chained callbacks (add via catch(func)
) called.
Here is the example:
const promise = new BetterPromise();
const promise2 = promise.catch(e => {
console.log('I got it!');
});
A bit more real-world example:
const promise = new BetterPromise((res, rej) => {
return fetch('...').then(res, rej);
})
.then(r => r.json())
.catch(e => {
console.error('Something went wrong', e);
});
promise.reject(new Error('Stop it! Get some help!'));
cancel
This method rejects the promise with CancelledError
.
new BetterPromise()
.catch(e => {
if (CancelledError.is(e)) {
console.log('Canceled');
}
})
.cancel();
resolve
This method resolves the promise with the specified result.
const promise = new ManualPromise();
promise.then(console.log);
promise.resolve('Done!');