Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stop promise chain with multiple catches

In Node.js, I need to read a file and validate it's contents, all in async. I m using Node.js 6.6, bluebird 3.4.6

Example code:

// pseudo function to read file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function readFile(flag) {
    return new Promise(function (resolve, reject) {
        console.log('Reading file...');
        if (flag) {
            resolve('File contents');
        } else {
            reject('readFile error');
        }
    });
}

// pseudo function to validate file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function validate(fileContents, flag) {
    return new Promise(function (resolve, reject) {
        console.log('Validating file: ', fileContents);
        if (flag) {
            resolve('Validate passed');
        } else {
            reject('validation failed');
        }
    });
}


readFile(false)
    .then(function (fileContents) {
        console.log('Successfully read the file:', fileContents);
        return fileContents;
    })
    .catch(function (fileReadErr) {
        console.log('Failed to read the file:', fileReadErr);
        throw fileReadErr; // or, return Promise.reject(err);
    })
    .then(function (fileContents) {
        return validate(fileContents, false);
    })
    .then(function (result) {
        console.log('Successfully validated the file:', result);
    })
    .catch(function (err) {
        console.log('Failed to validate the file:', err);
    })
    ;
<script src="https://cdn.jsdelivr.net/bluebird/3.4.6/bluebird.min.js"></script>

The above code will print

Reading file...
Failed to read the file: readFile error
Failed to validate the file: readFile error

The above promise chain roughly translates to below sync code:

try {
    let fileContents;

    try {
        fileContents = readFile(false);
        console.log('Successfully read the file:', fileContents);
    } catch (e) {
        console.log('Failed to read the file:', e);
        throw e;
    }

    let validationResult = validate(fileContents, false);
    console.log('Successfully validated the file:', validationResult);
} catch (err) {
    console.log('Failed to validate the file:', err);
}

And, throwing or rejecting in the first catch method will still invoke the 2nd catch method.

My question: Is there any way to break the chain once the file reading is failed? My objective is to return different HTTP status codes (file read error: 500, validation failed: 400) from an express.js route.

I know a solution using non-standard specialized catch method, but that requires special handling. In the sense, I need to throw errors or need some filtering key in the error object and both of which are not in my hands, and involves some work to achieve it. This solution is mentioned in bluebird docs & here: Handling multiple catches in promise chain

like image 410
manikanta Avatar asked Oct 21 '16 11:10

manikanta


People also ask

How do you handle multiple promises?

In this approach, we will use Promise. all() method which takes all promises in a single array as its input. As a result, this method executes all the promises in itself and returns a new single promise in which the values of all the other promises are combined together.

Does Promise all use multiple threads?

Often Promise. all() is thought of as running in parallel, but this isn't the case. Parallel means that you do many things at the same time on multiple threads. However, Javascript is single threaded with one call stack and one memory heap.

Does Try Catch handle Promise rejection?

The "invisible try.. catch " around the executor automatically catches the error and turns it into rejected promise. This happens not only in the executor function, but in its handlers as well. If we throw inside a .


1 Answers

The simplest solution by far is to use what I call "insulated catches". ie, a pattern in which each .catch() is a specialist, associated with a particular step in the overall process, and the main chain comprises only .thens (and eventually a single, terminal catch).

Also, it is useful in this kind of circumstance to convey added information down the error path by re-throwing Error objects with added properties. This avoids the need for custom Errors.

Promise.resolve()
.then(function() {
    return readFile(false)
    .then(function (fileContents) {
        console.log('Successfully read the file:', fileContents);
        return fileContents;
    })
    .catch(function (error) {
        error.code = 521; // or whatever
        error.customMessage = 'Failed to read the file';
        throw error;
    })
})
.then(function (fileContents) {
    return validate(fileContents, false)
    .then(function (result) {
        console.log('Successfully validated the file:', result);
        return fileContents;
    })
    .catch(function (error) {
        error.code = 522; // or whatever
        error.customMessage = 'Failed to validate the file';
        throw error;
    });
})
.catch(function(error) { // terminal catch.
    console.log(error); 
    // It's possible for unaugmented errors to reach this point,
    // so be sure to test for the extra properties before trying to use them.
    if(error.code) {...}
    if(error.customMessage) {...}
    // Note also that the original error.message is still intact.
});

The initial Promise.resolve() isn't strictly necessary, but helps keep everything else symetrical.

This will work with any Promises/A+ lib. Bluebird-sugar is not required.

like image 98
Roamer-1888 Avatar answered Nov 10 '22 15:11

Roamer-1888