1.3.0 • Published 1 year ago

completable-promise v1.3.0

Weekly downloads
5
License
MIT
Repository
github
Last release
1 year ago

CompletablePromise

branchbuildcoverage
mainBuild StatusCoverage Status
developBuild StatusCoverage Status

CompletablePromise allows to create a Promise instance that does not start its resolution upon its declaration.

Table of Contents

Installing

Using npm:

$ npm install completable-promise

Using bower:

$ bower install completable-promise

Using yarn:

$ yarn add completable-promise

Back to top

Usage

A CompletablePromise can be initialized as follows:

import { CompletablePromise } from "completable-promise";

const completablePromise = new CompletablePromise();

completablePromise.then(value => {
    console.log(value);
}).catch(reason => { 
    console.error(reason);
});

This kind of promise will remain in pending state until one among resolve or reject methods is explicitly called:

  • resolve will trigger the then transition

    completablePromise.resolve('foo');
  • reject will trigger the catch transition

    completablePromise.reject('error');

After the first resolve or reject call, future ones will be ignored:

const completablePromise = new CompletablePromise();

completablePromise.then(value => {
    console.log(value);    // foo
}).catch(reason => { 
    console.error(reason); // never printed out
});

completablePromise.resolve('foo');  // success
completablePromise.resolve('bar');  // ignored
completablePromise.reject('error'); // ignored

Back to top

CompletablePromise states

CompletablePromise states reflect the Promise states (more info can be found here).

Upon initialization, a CompletablePromise will be in pending state.

const completablePromise = new CompletablePromise();

console.log(completablePromise.getState());    // pending
console.log(completablePromise.isPending());   // true
console.log(completablePromise.isSettled());   // false

Upon calling resolve, the state will irreversibly change to fulfilled:

completablePromise.resolve('foo');

console.log(completablePromise.getState());    // fulfilled
console.log(completablePromise.isPending());   // false
console.log(completablePromise.isSettled());   // true

console.log(completablePromise.isFulfilled()); // true
console.log(completablePromise.isRejected());  // false

completablePromise.reject('error');

console.log(completablePromise.isFulfilled()); // true
console.log(completablePromise.isRejected());  // false

Upon calling reject, the state will irreversibly change to rejected:

completablePromise.reject('error');

console.log(completablePromise.getState());    // rejected
console.log(completablePromise.isPending());   // false
console.log(completablePromise.isSettled());   // true

console.log(completablePromise.isFulfilled()); // false
console.log(completablePromise.isRejected());  // true

completablePromise.resolve('foo');

console.log(completablePromise.isFulfilled()); // false
console.log(completablePromise.isRejected());  // true

Back to top

CompletablePromise antipattern solution

When using a CompletablePromise, the following antipattern (where errors should be explicitly handled within a try...catch) can arise:

const completablePromise = new CompletablePromise();

completablePromise.then(value => {
    console.log(value);    // never printed out
}).catch(reason => { 
    console.error(reason); // never printed out
});

const brokenJsonString = '{"foo":"bar"';
// try...catch antipattern
try {
    completablePromise.resolve(JSON.parse(brokenJsonString));
} catch (exception) {
    // fallback
}

Such thing does not happen with the classic Promise approach:

const brokenJsonString = '{"foo":"bar"';
const promise = new Promise((resolve, reject) => {
    resolve(JSON.parse(brokenJsonString));
});

promise.then(value => {
    console.log(value);    // never printed out
}).catch(reason => { 
    console.error(reason); // prints the failure reason
});

A solution to this problem is using tryResolve method:

const completablePromise = new CompletablePromise();

completablePromise.then(value => {
    console.log(value);    // never printed out
}).catch(reason => { 
    console.error(reason); // prints the failure reason
});

const brokenJsonString = '{"foo":"bar"';
completablePromise.tryResolve(() => {
    // put here all the code that might fail and should be eventually handled in the catch handler
    return JSON.parse(brokenJsonString);
});

Back to top

Mixing CompletablePromise and Promise

Sometimes there are situations where the results of more promises need to be aggregated with Promise.all, Promise.allSettled, Promise.any and Promise.race constructs.

For this purpose, the get method allows to retrieve the inner Promise instance of a CompletablePromise:

const completablePromise = new CompletablePromise();

const promise = new Promise((resolve, reject) => {
    resolve('bar');
});

Promise.all([completablePromise.get(), promise]).then(values => {
    console.log(values) // ['foo', 'bar']
});

completablePromise.resolve('foo');

Back to top

Example

A possible use case of this library is to promisify a function that is based on the callback approach, avoiding the callback hell/pyramid of doom problem.

The following example shows how to prompt multiple times the user for some input. Even if the CompletablePromise approach is a bit more elaborated, its result is surely clearer thanks to the chaining of the promises.

Common setup:

import { createInterface } from "readline";

const readLine = createInterface({
    input: process.stdin,
    output: process.stdout
});

Callback approach:

readLine.question('Step 1) Insert a value: ', value => {
    console.log(value);
    // perform other operations with the first input 
    readLine.question('Step 2) Insert another value: ', value => {
        console.log(value);
        // perform other operations with the second input
        readLine.question('Step 3) Insert once again a value: ', value => {
            readLine.close();
            console.log(value);
            // perform other operations with the third input
        });
    });
});

CompletablePromise approach:

import { CompletablePromise } from "completable-promise";

function readUserInput(query) {
    const completablePromise = new CompletablePromise();
    readLine.question(query, value => {
        completablePromise.resolve(value);
    });
    return completablePromise;
}

readUserInput('Step 1) Insert a value: ').then(value => {
    console.log(value);
    // perform other operations with the first input 
    return readUserInput('Step 2) Insert another value: ');
}).then(value => {
    console.log(value);
    // perform other operations with the second input
    return readUserInput('Step 3) Insert once again a value: ');
}).then(value => {
    readLine.close();
    console.log(value);
    // perform other operations with the third input
}).catch(reason => console.error(reason));

Back to top

Contributing

Contributions, issues and feature requests are welcome!

Back to top

License

This library is distributed under the MIT license.

Back to top