@krhkt/atomic-promise
v1.1.2
Published
Promise wrapper that can be resolved or rejected directly
Readme
Atomic Promise JS
A thenable promise wrapper that can be resolved or rejected by directly invoking methods on the instance.
It can be used interchangebly with the standard
Promise instance.
An atomic promise doesn't need to wrap async code on instantiation, providing an interface that allows it to be connected to
callbacks API more cleanly, by using .resolve() or .reject() at any point after its instantiation.
Usage
To create an atomic promise, just instantiate the class. The instance is nothing more than
a wrapper of an internal promise that exposes .resolve() and .reject() as interface
methods.
import { AtomicPromise } from '@krhkt/atomic-promise';
const atom = new AtomicPromise();
// add chain
atom.then(number => number + 1)
.then(number => number + 2)
.then(number => console.log('number:', number));
// resolving the wrapped promise
atom.resolve(3);
// ...
// > number: 6await an AtomicPromise
It's possible to await an AtomicPromise since it's a thenable object.
const atom = new AtomicPromise();
setTimeout(() => atom.resolve(), 1000);
await atom;
console.log('timer done');resolve() and reject() always return the AtomicPromise instance, so they can be used anywhere in the promise chain.
const atom = new AtomicPromise();
await atom
.then((arg) => console.log('first: ', arg));
.resolve(10)
.then(() => console.log('second'));
// > first: 10
// > secondNOTE:
remember to onlyawaitan atomic promise if it's already resolved or if an async code will resolve/reject it later.
// DON'T DO THIS:
const atom = new AtomicPromise();
atom.then(() => console.log('all good'));
await atom; // will await forever
atom.resolve(); // will never be executedUsing AtomicPromise to simplify callback APIs
Instead of creating a lambda function wrapping a callback API, it's possible to simply use the instance in the closure.
const atom = new AtomicPromise();
setTimeout(() => atom.resolve(), 1000);
// for comparison, using the defaut Promise would result in wrapping
// the async code in a lambda, resulting in something like:
const promise = new Promise(
(resolve, _) => setTimeout(resolve, 1000)
);Example binding .resolve and .reject in a callback:
const atom = new AtomicPromise();
atom.then((rows) => console.log(rows))
.catch((err) => console.log('error updating:', err));
// callback
sqlit3Db.run(
'UPDATE tbl SET name = $name WHERE id = $id',
{ $id: 2, $name: 'bar' },
(err, rows) => (err) ? atom.reject(err) : atom.resolve(rows)
);It's also possible to plug an AtomicPromise directly to a callback by using .boundResolve.
const atom = new AtomicPromise();
setTimeout(atom.boundResolve, 1000);
console.log('timer running...');
await atom;
console.log('execution resumed');
// > timer running...
// > execution resumed.boundResolve can also be used to unwrap the callback arguments.
The API of Promise.resolve() only accepts a single argument, so the arguments
of the callback will always be passed as an array, but it's possible to take advantage of the
destructuring assignment to access the arguments more easily:
const atom = new AtomicPromise();
const asyncTask = (callback) => {
setTimeout(() => {
callback('all done!');
}, 1);
};
asyncTask(atom.boundResolve); // supplying the callback
const [result] = await atom;
// result will contain the string 'all done!'Ignoring arguments:
const atom = new AtomicPromise();
sqlit3Db.run(
'select * from tbl where id=?', 1,
atom.boundResolve // callback
);
const [_, row] = await atom;
// retrieved row
console.log(row);Another example:
const atom = new AtomicPromise();
sqlit3Db.run(
'UPDATE tbl SET name = $name WHERE id = $id',
{ $id: 2, $name: 'bar' },
atom.boundResolve, // callback
);
const [err, row] = await atom;
console.log(err, row);
// ALTERNATIVELY, the same can be achieved using .then()
atom.then(([err, row]) => {
console.log(row);
});NOTE:
The.boundResolveand.boundRejectinstance lambdas will always pipe multiple arguments of callbacks as an array to.then,.catch, and.finallycallbacks.
If a callback only contains an error argument, it's possible to use a .boundError to make the error capturable in a try...catch statement.
const atom = new AtomicPromise();
fs.accept(filePath, fs.constants.F_OK, atom.boundError);
try {
await atom;
// if the callback didn't have any error argument, the promise is resolved
// (... code to run if the callback had no error...)
} catch (err) {
// if the error argument was present, the promise is rejected triggering an exception
// (... code to run if the callback had an error...)
}Chaining AtomicPromise and Promise
The instance can also be used to chain atomic and standard promises:
const atom = new AtomicPromise();
const promiseA = new Promise((resolve, _) => {
setTimeout(() => resolve('promiseA resolved'), 1000);
});
const promiseB = new Promise((resolve, _) => {
setTimeout(() => resolve('promiseB resolved'), 2000);
});
await promiseA
.then((result) => console.log('1st:', result))
.then(() => atom.resolve('atom resolve'))
.then((result) => console.log('2nd:', result))
.then(() => promiseB)
.then((result) => console.log('3rd:', result));
// > 1st: promiseA resolved
// > 2nd: atom resolve
// > 3rd: promiseB resolvedOr, when resolving the atomic promise asynchronously:
const atom = new AtomicPromise();
const promiseA = new Promise((resolve, _) => {
setTimeout(() => resolve('promiseA resolved'), 1000);
});
setTimeout(() => atom.resolve('atom resolved'), 2000);
const promiseB = new Promise((resolve, _) => {
setTimeout(() => resolve('promiseB resolved'), 3000);
});
await promiseA
.then((result) => console.log('1st:', result))
.then(() => atom)
.then((result) => console.log('2nd:', result))
.then(() => promiseB)
.then((result) => console.log('3rd:', result));
// > 1st: promiseA resolved
// > 2nd: atom resolved
// > 3rd: promiseB resolvedAPI
Constructor
AtomicPromise(): creates a new AtomicPromise object. The constructor doesn't accept any parameter.
Static methods
AtomicPromise.resolve(payload): simply creates a resolved AtomicPromise. Only use this method for class bound integrations. Prefer the standard Promise.resolve() if possible.
AtomicPromise.reject(payload): simply creates a rejected AtomicPromise. Only use this method for class bound integrations. Prefer the standard Promise.reject() if possible.
Instance properties
.id: returns an integer uniquely identifying the instance. It's a simple auto-increment number that wraps when it gets
close to the MAX_SAFE_INTEGER.
isFulfilled: boolean, returns true if the promise is resolved or rejected, or false if the promise is pending.
isResolved: boolean, returns true if the promise is resolved, false otherwise.
isRejected: boolean, returns true if the promise is rejected, false otherwise.
.boundResolve: a lambda that is bound to the instance that can be used directly as a callback to resolve the promise.
.unwrappedResolve: same as .boundResolve, but if the callback has only one argument, there's no need to use the destructuring assignment.
.boundReject: a lambda that is bound to the instance that can be used directly as a callback to reject the promise.
.unwrappedReject: same as .boundReject, but if the callback has only one argument, there's no need to use the destructuring assignment.
.boundError: a lambda that will reject the promise if the first argument is a non falsy value, otherwise will resolve the promise successfully.
Instance methods
.resolve(): will resolve the underlying promise with the provided argument. Returns the AtomicPromise instance.
.reject(): will reject the underlying promise with the provided argument. Returns the AtomicPromise instance.
.then(): delegates the argument to the .then method of the underlying promise. Returns the AtomicPromise instance.
.catch(): delegates the argument to the .catch method of the underlying promise. Returns the AtomicPromise instance.
.finally(): delegates the argument to the .finally method of the underlying promise. Returns the AtomicPromise instance.
.asPromise(): returns the underlying promise. This method is useful to integrate with third party code that expects a standard promise instance.
