Bluebird Javascript Promise: stop execution after dealing with specified error

1.4k views Asked by At

I've got a somewhat complicated flow that receives an entryUrl from a database, checks where it redirects to, and then updates it with an exitUrl.

Basically the flow should be like so:

  • retrieve an Url without an exit url
    • get the Url.entryUrl's headers using request
      • if there's an unexpected response or connection was reset, flag this Url and continue with the next one
    • parse the exitUrl resulting from the request performed
      • store the exitUrl
      • continue with the next Url
  • if no Url available, try again after 5 seconds
  • if any unexpected error in the above chain or subchains, try again after 60 seconds

My current implementation is like so, using the Bluebird javascript promise style:

function processNext() {
    return api.getUrlWithoutExitUrl()
    .then(function followEntryUrl(url)
    {
        if (!url || !url.entryUrl)
        {
            throw new NoUrlAvailableError();
        }

        log.info('getting exit url for ' + url.entryUrl);
        return [
            request({
                method              : 'HEAD',
                url                 : url.entryUrl,
                followAllRedirects  : true,
                maxRedirects        : 20
            })
            .catch(ResponseError, function()
            {
                log.error('got strange response');
            })
            .catch(ConnResetError, function()
            {
                log.error('connection was reset');
            })
            .then(function removeInvalidUrl()
            {
                log.info('remove invalid url'); //FIXME: after doing this, we should not continue with the other `then` calls
            }),
            url
        ];
    })
    .spread(function parseExitUrl(res, url)
    {
        if (!res[0] || !res[0].request || !res[0].request.uri || !res[0].request.uri.href)
        {
            throw new InvalidUrlError();
        }
        return [res[0].request.uri, url];
    })
    .spread(function storeExitUrl(parsedExitUrl, url)
    {
        return api.setUrlExitUrl(url, parsedExitUrl);
    })
    .then(processNext)
    .catch(InvalidUrlError, function()
    {
        log.info('an attempted url is invalid, should set as processed and continue with next immediately');
    })
    .then(processNext)
    .catch(NoUrlAvailableError, function()
    {
        log.info('no url available, try again after a while');
    })
    .delay(5000)
    .then(processNext)
    .catch(function(err)
    {
        log.error('unexpected error, try again after a long while');
        log.error(err);
        log.error(err.constructor);
    })
    .delay(60000)
    .then(processNext);
}
processNext();

function ResponseError(e)
{
    return e && e.code === 'HPE_INVALID_CONSTANT';
}

function ConnResetError(e)
{
    return e && e.errno === 'ECONNRESET';
}

Now, the problem is that if there's a ConnResetError or a ResponseError, the catch blocks are executed as they should be, but the then blocks following the spread call are also executed -- yet I want execution to stop after having done something after catching these 2 specific error types.

How would I achieve such flow of execution?

2

There are 2 answers

7
Benjamin Gruenbaum On

Just like in synchronous code - if you have a catch in which you want to perform some processing and then propagate the error - you can rethrow it:

Synchronous code:

try {
    a = makeRequest();
} catch(e) { 
    // handle
    throw e;
}

With promises:

makeRequest().catch(e => {
    // handle
    throw e; // also a good idea to add data to the error here
});
1
yoz On

From your inner promise, when you first catch the ResponseError or ConnResetError, you return normally (i.e. not throwing), so the subsequent promise chain succeeds, executing its then() and spread() branches, rather than failing and going to the catch() branches.

You probably want to rewrite your inner promise catch blocks like so:

...
.catch(ResponseError, function(err) {
     log.error('got strange response');
     throw err;
})
...

Basically, re-throw the Error you have caught if you want to continue treating it as an error.