Nested Promise is not propagating error to parent

2019-01-29 13:51发布

问题:

I'm creating an API using Node.js/TypeScript running Express. Below is an excerpt from my get method. An error is being triggered in the format method, which throws an error that is caught by the promise, but not propagated to the parent promise after a throw:

            this.getModel(objectName).findAll(queryParameters).then(function(databaseObjects) {
                for (let databaseObject of databaseObjects) {
                    var jsonObject = {};
                    //console.log("Database object: ");
                    //console.log(databaseObject);
                    transform.baseFormat(databaseObject, jsonObject)
                    .then(() => transform.format(databaseObject, jsonObject))
                    .then(() => {
                        res.locals.retval.addData(jsonObject);
                    }).catch((e) => {
                        console.log("Caught error during format of existing object: ");
                        console.log(e);
                        throw e;
                    });
                }
            })
            .then(() => {
                if (metadata) {
                    this.metadata(objectName, false, transform, res.locals.retval);

                    delete queryParameters.limit;
                    delete queryParameters.offset;
                    console.log("RUNNING METADATA COUNT: ");
                    this.getModel(objectName).count(queryParameters).then(function(count) {
                        res.locals.retval.setMetadata("records", count);
                        return next();
                    }).catch(function(e) {
                        this.error(e, res);
                        return next();
                    });
                } else {
                    console.log("NO METADATA");
                    return next();
                }
            })
            .catch((e) => {
                // TODO: Move status into error() function
                console.log("500 Error on GET");
                console.error(e);
                res.locals.retval.addError(ErrorCode.InternalError, e);
                res.status(ErrorCode.InternalError).send(res.locals.retval);
                return next();
            });

Here's the output:

(node:8277) Warning: a promise was created in a handler at /Library/WebServer/adstudio/dist/server.js:555:51 but was not returned from it, see
at Function.Promise.bind (/Library/WebServer/adstudio/node_modules/bluebird/js/release/bind.js:65:20)
Caught error during format of existing object: 
Test Error
END FUNCTION HAS BEEN REACHED!

Then the request fails to finish.

I've read a lot on Promises and I haven't been able to find an issue/solution similar to mine.

  • http://bluebirdjs.com/docs/warning-explanations.html
  • http://taoofcode.net/promise-anti-patterns/
  • https://www.reddit.com/r/javascript/comments/4bj6sm/am_i_wrong_to_be_annoyed_with_promise_error/
  • https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
  • Chained promises not passing on rejection
  • http://wiki.commonjs.org/wiki/Promises/A
  • https://promisesaplus.com/

回答1:

Running inside that for-loop is not asynchronous, so your promise is resolving basically as soon as the loop finishes, yet before all your formatting finishes.

Use a promise control flow, like bluebird's Promise.each which is serial or just Promise.all. Then any exceptions will be caught.

this.getModel(objectName).findAll(queryParameters).then(function (databaseObjects) {
  var promises = databaseObjects.map(databaseObject => {
    var jsonObject = {}
          // console.log("Database object: ");
          // console.log(databaseObject);
    return transform.baseFormat(databaseObject, jsonObject)
          .then(() => transform.format(databaseObject, jsonObject))
          .then(() => {
            res.locals.retval.addData(jsonObject)
          }).catch((e) => {
            console.log('Caught error during format of existing object: ')
            console.log(e)
            throw e
          })
  })
  return Promise.all(promises)
})
.catch((e) => {
    // TODO: Move status into error() function
  console.log('500 Error on GET')
  console.error(e)
  res.locals.retval.addError(ErrorCode.InternalError, e)
  res.status(ErrorCode.InternalError).send(res.locals.retval)
  return next()
})