Confusing by promise object's error chain [duplicate] - javascript

This question already has answers here:
Chained promises not passing on rejection
(4 answers)
Closed 7 years ago.
I'm using promise object in node.js. I have a object:
var Send = {
send(post_ids) {
return Post.findById(post_ids)
.then((posts) => {
return sendArticlesToWechat(setupArticles(posts)); // sendArticlesToWechat is also a promise
})
.then((result) => {
console.log("RESULT: " + result);
})
.catch((err) => {
console.error("SEND ERROR: " + err);
return err;
});
},
}
export default Send;
and call its method in another file:
Send.send(req.body)
.then((result) => {
console.log("CALL SEND: " + result);
})
.catch((err) => {
console.error(err);
});
When an error occurs, I got two output:
SEND ERROR: ERROR: // error message
CALL SEND: ERROR: // error message
This error occurred in the sendArticlesToWechat() function which be returned. Because it's a promise too so I can catch its error outside. This is what I expected.
When I call the Send.send(), I expected to get the error in catch(), but the error appears in the then() method.
According to the output, the error did returned from the previous catch(), why I can not keep it in the catch()?

The problem is in your final catch(). Because you return err, you cause the promise to become resolved instead of rejected. If you want to return a rejected promise, then either remove the catch() or re-throw err
var Send = {
send(post_ids) {
return Post.findById(post_ids)
.then((posts) => {
return sendArticlesToWechat(setupArticles(posts)); // sendArticlesToWechat is also a promise
})
.then((result) => {
console.log("RESULT: " + result);
})
.catch((err) => {
console.error("SEND ERROR: " + err);
//return err;//converts from reject to resolved
throw err;
});
},
}
export default Send;

Related

Why is `.then()` processed when fetch fails?

Consider the following code where I tried to shield fetch against any unsuccessful connections (I call them non "200-ish" in the comments) and provide a function that will make use of its successful results:
const callApi = () => {
return fetch("http://doesnotexist.example.com")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.then((r) => r.json())
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
The result is
call failed: TypeError: Failed to fetch
the call was successful
Since this is a network issue, the first then() was not executed and we jumped directly to the catch(). But why has the last then() been executed?
The next example is for a call that returns an error code:
const callApi = () => {
return fetch("https://httpstat.us/500")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.then((r) => r.json())
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
The output is
status for failed call: 500
call failed: Error: Internal Server Error (500)
the call was successful
Same question as above.
Finally, for 200 everything is fine:
const callApi = () => {
return fetch("https://httpstat.us/200")
.then((r) => {
// check for non200-ish respnses (404, etc.)
if (!r.ok) {
console.log(`status for failed call: ${r.status}`);
throw new Error(`${r.statusText} (${r.status})`);
} else {
// continue the chain because the result is 200-ish
return r;
}
})
.catch((err) => {
// should catch network errors (DNS, etc.) as well as replies that are not 200-ish
console.log(`call failed: ${err}`);
});
};
callApi().then((r) => console.log("the call was successful"));
Another way to address the question would be: how to stop processing at the catch()?
You're returning the result of a fetch().then().catch() chain, and calling a .then() on that:
callApi().then((r) => console.log("the call was successful"));
That last .then() will always be executed, because the promise was handled successfully. It either:
Completed successfully, or
catch took care of any errors that occurred`

Promise.reject in .then() returning undefined

I've currently got an ES6 class with a constructor and two methods. I'm a tad confused as to why using Promise.reject(ex) within the .then() is resolving undefined. If someone wouldn't mind explaining what I'm doing wrong that would be much appreciated.
I have a method called getYaml() which contains the following:
_getYaml(recordId) {
return new Promise((resolve, reject) => {
fs.readFile(this.workingDir + '/' + recordId + '.yaml', 'utf8', function(err, data) {
if (err) reject(err)
resolve(data)
})
})
}
I then have another method called getCompDoc which makes use of the other method like so:
getCompDoc(recordId) {
return this._getYaml(recordId).then(data => {
let yaml = data
let yamlObj
try {
yamlObj = YAML.safeLoad(yaml)
} catch (ex) {
ex.message = `Failure to parse yaml. Error: ${ex.message}`
logger.error(ex.message, {}, ex)
return Promise.reject(ex)
}
let compDoc = {
// ...
}
return compDoc
}).catch(err => {
logger.error(err, {}, err)
})
}
I then have a test to check that the YAML parsing error is caught and then a promise rejected which looks like so:
describe('error cases', () => {
const fakeRecordId = 'SomeYaml'
beforeEach(() => {
sinon.stub(myClass, '_getYaml').returns(Promise.resolve('{{&^%}egrinv&alidgj%^%^&$£#£#£}'))
})
afterEach(() => {
myClass._getYaml.restore()
})
it('Error parsing yaml, rejects with error', () => {
return expect(myClass.getCompDoc(fakeRecordId)).to.be.rejected.then(response => {
expect(response.message).to.match(/Failure to parse yaml. Error: /)
})
})
})
Test output:
AssertionError: expected promise to be rejected but it was fulfilled with undefined
If I simply return the exception that is thrown within the getCompDoc method, I recieve the error as expected, however as soon as I use Promise.reject it resolves with undefined.
I was thinking of wrapping the getCompDoc in a return new Promise() however I wasn't sure if this would be an example of the Promise constructor anti-pattern. I would ideally like to reject this, instead of returning the error directly as then I can assert that the method was rejected and not fulfilled.
You 'swallow' the error in getCompDoc in your catch clause. Specifically, here's a simplified snippet representing your code:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => console.error('getYamlPromise', error));
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));
As you can see, getCompDocPromise is resolved with undefined. If you would like to propagate the error, your catch clause will have to throw a new error or return a rejected promise:
let getYamlPromise = Promise.reject('REJECTED!');
let getCompDocPromise = getYamlPromise
.then(data => console.log('getYamlPromise', data))
.catch(error => {
console.error('getYamlPromise', error);
return Promise.reject(error);
});
getCompDocPromise
.then(a => console.log('getCompDocPromise RESOLVED', a))
.catch(a => console.log('getCompDocPromise REJECTED', a));

How do I fix a race condition in a nested Promise in Node.js? [duplicate]

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/
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()
})

Angular 2 chained Promise and passing on reject

Should be a simple question, however I can find no documentation on how to do it.
Chaining a promise like this:
// Making a promise, no problem
let promise = new Promise((resolve, reject) => {
let data = {}; // do something
reject(data);
});
// First chain, no problem
promise.then(data => {
console.log('Success callback', data); // This will not be called
}).catch(err => {
console.log('Error callback', err); // This WILL be called
})
// Second chain, acts as resolved
.then(data => {
console.log('Level 2 success', data); // This WILL be called
}).catch(err => {
console.log('Level 2 error', err); // This will NOT be called
});
I understand that unless I pass on the rejection from the first catch callback, then subsequent then's will be handled as resolved.
So... how to call reject from within the catch function?
See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
You need to throw an exception in the catch handler for example to reject the second chain.
// First chain, no problem
promise.then(data => {
console.log('Success callback', data); // This will not be called
}).catch(err => {
console.log('Error callback', err); // This will NOT be called
throw "zonk";
})

Error while using promisefiy all

I use promisifyAll to the following module since I want to use it with promises and I got error "TypeError: Cannot read property 'then' of undefined"
const DBWrapper = Promise.promisifyAll(require("node-dbi").DBWrapper);
var dbWrapper = new DBWrapper('pg', dbConnectionConfig);
dbWrapper.connect();
dbWrapper.insert('USERS', data, function (err, data) {
if (err) {
console.log("error to insert data: " + err);
} else {
console.log("test" + data);
}
}).then(() => {
//read data
dbWrapper.fetchAll("SELECT * FROM USERS", null, function (err, result) {
if (!err) {
console.log("Data came back from the DB.", result);
} else {
console.log("DB returned an error: %s", err);
}
dbWrapper.close(function (close_err) {
if (close_err) {
console.log("Error while disconnecting: %s", close_err);
}
});
});
})
You have two things going on here that are incorrect from what I can tell.
You're not properly implementing the promises in the above code. Error first callbacks aren't passed in when invoking Promise returning methods, instead they pass the result value to the nearest .then(). If an error occurs they will pass that to the nearest .catch(). If there is no .catch() and an error occurs, an unhandledRejection error will be thrown.
By default, promisifyAll() appends the suffix Async to any promisified methods, so you will need to modify any method calls on dbWrapper accordingly.
Assuming node-dbi can be promisifed and that your DB calls are correct the following code should work as you were initially anticipating
const Promise = require('bluebird');
const DBWrapper = require("node-dbi").DBWrapper;
const dbWrapper = Promise.promisifyAll(new DBWrapper('pg', dbConnectionConfig));
return dbWrapper.insertAsync('USERS', data)
.then((data) => {
console.log("test" + data);
//read data
return dbWrapper.fetchAllAsync("SELECT * FROM USERS", null)
})
.then((result) => {
console.log('Data came back from DB.', result);
return dbWrapper.closeAsync();
})
.catch((err) => {
console.log('An error occurred:', err);
});

Categories

Resources