npm package: @krhkt/atomic-promise
Description: Promise wrapper that can be resolved or rejected directly
Author: @krhkt
Latest version: 1.1.2

# Atomic Promise JS

A [thenable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables) promise wrapper that can be resolved or rejected by directly invoking methods on the instance.
It can be used interchangebly with the standard 
[`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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.

```js
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: 6
```

### `await` an `AtomicPromise`

It's possible to `await` an `AtomicPromise` since it's a [`thenable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables) object.

```js
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.

```js
const atom = new AtomicPromise();
await atom
    .then((arg) => console.log('first: ', arg));
    .resolve(10)
    .then(() => console.log('second'));

// > first: 10
// > second
```

> **NOTE**:  
> remember to only `await` an atomic promise if it's already resolved or if an async code will resolve/reject it later.

```js
// DON'T DO THIS:
const atom = new AtomicPromise();
atom.then(() => console.log('all good'));

await atom; // will await forever

atom.resolve(); // will never be executed
```

### Using `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.

```js
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:
```js
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`.

```js
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()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring) to access the arguments more easily:

```js
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:

```js
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:
```js
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 `.boundResolve` and `.boundReject` instance lambdas will always pipe multiple arguments of callbacks as an array to `.then`, `.catch`, and `.finally` callbacks.

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. 

```js
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:

```js
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 resolved
```

Or, when resolving the atomic promise asynchronously:

```js
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 resolved
```

-----

## API

### 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.