Bluebird Javascript Promise: stop execution after dealing with specified error - javascript

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?

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
});

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.

Related

What happens if i don't resolve a Promise? [duplicate]

I have a scenario where I am returning a promise.
The promise is basically triggered by an ajax request.
On rejecting the promise it shows an error dialog that there is a server error.
What I want to do is when the response code is 401, I neither want to resolve the promise nor reject it (because it already shows the error dialog). I want to simply redirect to the login page.
My code looks something like this:
function makeRequest(ur, params) {
return new Promise(function (resolve, reject) {
fetch(url, params).then((response) => {
let status = response.status;
if (status >= 200 && status < 300) {
response.json().then((data) => {
resolve(data);
});
} else {
if (status === 401) {
redirectToLoginPage();
} else {
response.json().then((error) => {
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
reject({ status, error });
});
}
}
});
});
}
As you can see, if the status is 401, I am redirecting to the login page. The promise is neither resolved nor rejected.
Is this code OK, or is there any better way to accomplish this?
A promise is just an object with properties in Javascript. There's no magic to it. So failing to resolve or reject a promise just fails to ever change the state from "pending" to anything else. This doesn't cause any fundamental problem in Javascript because a promise is just a regular Javascript object. The promise will still get garbage collected (even if still pending) if no code keeps a reference to the promise.
The real consequence here is what does that mean to the consumer of the promise if its state is never changed? Any .then() or .catch() listeners for resolve or reject transitions will never get called. Most code that uses promises expects them to resolve or reject at some point in the future (that's why promises are used in the first place). If they don't, then that code generally never gets to finish its work.
Or if code is using await on the promise instead of .then(), then that function will just remain suspended forever on that await. The rest of the function will be dead code and will never execute.
It's possible that you could have some other code that finishes the work for that task and the promise is just abandoned without ever doing its thing. There's no internal problem in Javascript if you do it that way, but it is not how promises were designed to work and is generally not how the consumer of promises expect them to work.
As you can see if the status is 401, I am redirecting to login page.
Promise is neither resolved nor rejected.
Is this code OK? Or is there any better way to accomplish this.
In this particular case, it's all OK and a redirect is a somewhat special and unique case. A redirect to a new browser page will completely clear the current page state (including all Javascript state) so it's perfectly fine to take a shortcut with the redirect and just leave other things unresolved. The system will completely reinitialize your Javascript state when the new page starts to load so any promises that were still pending will get cleaned up.
I think the "what happens if we don't resolve reject" has been answered fine - it's your choice whether to add a .then or a .catch.
However, Is this code OK? Or is there any better way to accomplish this. I would say there are two things:
You are wrapping a Promise in new Promise when it is not necessary and the fetch call can fail, you should act on that so that your calling method doesn't sit and wait for a Promise which will never be resolved.
Here's an example (I think this should work for your business logic, not 100% sure):
const constants = {
SERVER_ERROR: "500 Server Error"
};
function makeRequest(url,params) {
// fetch already returns a Promise itself
return fetch(url,params)
.then((response) => {
let status = response.status;
// If status is forbidden, redirect to Login & return nothing,
// indicating the end of the Promise chain
if(status === 401) {
redirectToLoginPage();
return;
}
// If status is success, return a JSON Promise
if(status >= 200 && status < 300) {
return response.json();
}
// If status is a failure, get the JSON Promise,
// map the message & status, then Reject the promise
return response.json()
.then(json => {
if (!json.message) {
json.message = constants.SERVER_ERROR;
}
return Promise.reject({status, error: json.message});
})
});
}
// This can now be used as:
makeRequest("http://example", {})
.then(json => {
if(typeof json === "undefined") {
// Redirect request occurred
}
console.log("Success:", json);
})
.catch(error => {
console.log("Error:", error.status, error.message);
})
By contrast, calling your code using:
makeRequest("http://example", {})
.then(info => console.log("info", info))
.catch(err => console.log("error", err));
Will not log anything because the call to http://example will fail, but the catch handler will never execute.
As others stated it's true that it's not really an issue if you don't resolve/reject a promise. Anyway I would solve your problem a bit different:
function makeRequest(ur,params) {
return new Promise(function(resolve,reject) {
fetch(url,params)
.then((response) => {
let status = response.status;
if (status >= 200 && status < 300) {
response.json().then((data) => {
resolve(data);
})
}
else {
reject(response);
}
})
});
}
makeRequest().then(function success(data) {
//...
}, function error(response) {
if (response.status === 401) {
redirectToLoginPage();
}
else {
response.json().then((error) => {
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
//do sth. with error
});
}
});
That means I would reject every bad response state and then handle this in your error handler of your makeRequest.
It works and isn't really a problem, except when a caller of makeRequest expects of promise to fulfil. So, you're breaking the contract there.
Instead, you could defer the promise, or (in this case) reject with status code/error.
The ECMAScript spec explains the purpose of promises and new Promise():
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
25.6.3.1 Promise ( executor )
NOTE The executor argument must be a function object. It is called for initiating and reporting completion of the possibly deferred action represented by this Promise object.
You should use promises to obtain future values. Furthermore, to keep your code concise and direct, you should only use promises to obtain future values, and not to do other things.
Since you’ve also mixed program control flow (redirection logic) into your promise’s “executor” logic, your promise is no longer “a placeholder for the results of a computation;” rather, it’s now a little JavaScript program masquerading as a promise.
So, instead of wrapping this JavaScript program inside a new Promise(), I recommend just writing it like a normal JavaScript program:
async function makeRequest(url, params) {
let response = await fetch(url, params);
let { status } = response;
if (status >= 200 && status < 300) {
let data = await response.json();
successLogic(data);
} else if (status === 401) {
redirectToLoginPage();
} else {
let error = await response.json()
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
errorLogic({ status, error });
}
}

JS: Complex Promise chaining

I'm faced with a small issue when trying to chain complex function calls with Promises and callbacks.
I have a main function, which calls subroutines. In these routines API calls are made.
For Example:
function handle(){
new Promise(function(resolve, reject){
let result = doAPICall1()
if (result === true) resolve(true);
reject(JSON.stringify(result))
}).then(function(){
let result = doAPICall2()
if (result === true) return true
throw new Error(JSON.stringify(result))
}).catch(error){
console.error(JSON.stringify(error))
}
}
function doAPICall1(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
function doAPICall2(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
But when I execute this example, doAPICall2 will be executed, while doAPICall1 is still running.
It only occures when long running calls are made.
Does anyone can give me a hint? Thank you!
You're overdoing manually a lot of things that Promises already do for you:
axios.get already returns a Promise, so there is no point in return a the response when it resolves and return a false when it rejects. A catch handler at the end of a Promise chain already handles all errors that may arise during the chain, so you don't need to catch every Promise.
I would do something like:
function doAPICall1(){
return axios.get('...');
}
function doAPICall2(){
return axios.get('...');
}
function handle(){
// in case you would use the api calls results.
let firstResult = null;
let secondResult = null;
return doAPICall1()
.then(res => {firstResult = res})
.then(() => doAPICall2())
.then(res => {
secondResult = res;
return []
})
}
I guess you will use the Api calls results for something. With the code above, you could consume the handle() function like follows:
function someSortOfController(){
handle().then(results => {
console.log(results[0]); // first api call result
console.log(results[1]); // second api call result
})
.catch(err => {
// here you will find any error, either it fires from the first api call or from the second.
// there is *almomst* no point on catch before
console.log(err);
})
}
There, you will access any error, either it came from the first api call or the second. (And, due to how Promises work, if the first call fails, the second won't fire).
For more fine grained error control, you may want to catch after every Promise so you can add some extra logs, like:
function doAPICall1(){
return axios.get('...')
.catch(err => {
console.log('the error came from the first call');
throw err;
});
}
function doAPICall2(){
return axios.get('...')
.catch(err => {
console.log('the error came from the second call');
throw err;
});
}
Now, if the first api call fails, everything will work as before (since you're throwing the error again in the catch), but you have more control over error handling (maybe the error returning from API calls is not clear at all and you want this kind of control mechanism).
 Disclaimer
This answer doesn't answer why your code acts like it does. However, there are so much things wrong in your code, so I think providing you with an example about using Promises is more valuable.
Don't worry and take some time to understand Promises better. In the example code below, doAPICall function return a Promise which resolves to a value, not the value itself.
function handle() {
doAPICall().then(result => {
//do something with the result
}).catch(error => {
//catch failed API call
console.error(error)
})
}
doAPICall() {
// this returns a Promise
return axios.get(...)
}

How can I force this promise to throw specific errors?

The code is a part of a much bigger and complicated code, so I am just gonna put the relevant snippets to my question.
I have this promise.all snippet:
Promise.all(receivedObjs.arrayCMsIds.map(cmid =>
server.writeAttachedCMinfo(invId,cmid)))
.then(function (results) { // for promise all
return res.json(apiHelp.success(results,"success"));
}).catch(function (error) {
res.json(apiHelp.error(error, error));
});
And this long complicated writeAttachedCMinfo function:
server.writeAttachedCMinfo = function (invId,cmid) {
return new Promise(function (resolve, reject) {
console.log("writeAttachedCMinfo");
console.log("invoiceId " + invId);
console.log("cmid "+ cmid);
var invoiceId = JSON.stringify(invId);
var cmId = JSON.stringify(cmid);
var invIdString = invoiceId;
var cmIdString = cmId;
invIdString = invIdString.slice(1, -1);
cmIdString = cmIdString.slice(1, -1);
var projection = 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos';
ubiqInvoice.findById(invIdString, projection).then(function (dbInvoice) {
var intInvCertifiedAmount = parseInt(dbInvoice.certifiedInvoiceAmount);
creditMemo.findById(cmIdString).then(function (dbCreditMemo) {
var intCreditMemoAmount = parseInt(dbCreditMemo.creditMemoAmount);
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
if (dbCreditMemo.isAssociated) {
return new Error ('CMisAssociated');
}
if (dbInvoice.gwCode === "100000000000"
|| dbInvoice.gwCode === "110000000000"
|| dbInvoice.gwCode === "111200000000"
|| dbInvoice.gwCode === "111100000000"
|| dbInvoice.gwCode === "111110000000"
) {
var creditMemoEntry = {
id: guid.create().value,
batchId: dbCreditMemo.batchId,
invoiceId: dbInvoice._id,
recordTypeCode: "CM",
buyerReference: dbInvoice.buyerReference,
supplierReference: dbInvoice.supplierReference,
creditMemoNo: dbCreditMemo.creditMemoNo,
creditMemoIssuingDate: dbCreditMemo.creditMemoIssuingDate,
creditMemoEffectiveDate: dbCreditMemo.creditMemoEffectiveDate,
lastModificationDate: dbCreditMemo.lastModificationDate,
currencyCode: dbCreditMemo.currencyCode,
creditMemoAmount: dbCreditMemo.creditMemoAmount,
hashCode: dbCreditMemo.hashCode,
description: dbCreditMemo.description,
uploadDate: dbCreditMemo.uploadDate,
isAssociated: true,
}
dbInvoice.creditMemos.push(creditMemoEntry);
dbInvoice.certifiedInvoiceAmount = dbInvoice.certifiedInvoiceAmount - dbCreditMemo.creditMemoAmount;
dbInvoice.save();
dbCreditMemo.isAssociated = true;
dbCreditMemo.save();
resolve(dbInvoice)
}
else { return new Error ('wrongggwcode'); }
})
});
}), function (error) {
console.log("error: " + error);
}
}
My goal, is to force error throwing in case one of the if conditions aren't met, and I want to pass the error to client in a form of a custom message so i can use it on the client said for displaying various errors , such as 'CMisbiggerThanInvoice'
if (intInvCertifiedAmount <= intCreditMemoAmount) {
console.log('cm bigger than invoice')
return new Error ('CMisbiggerThanInvoice');
}
I am just trying to figure out a way to pass the error from the writeAttachedCMinfo function to the promise.all's .catch(function (error) but it's not working, the promise.all is always returning success even if one of the if conditions aren't met.
I have tried reject('CMisbiggerThanInvoice'), reject(new Error('CMisbiggerThanInvoice')...all the same.
how can i really force the promise function to return an error?
In the context of a promise you should actually throw the error:
throw new Error('wrongggwcode');
If this executes in a promise constructor callback or then callback, it can be caught via the catch method (or second argument of then) and the argument of the callback you pass to it will be the error (object).
Calling reject from within a then callback will obviously not work, since you don't have access to reject there, but it will work in a promise constructor callback.
Simple example:
new Promise((resolve, reject) => {
setTimeout( () => reject(new Error('this is an error')) );
}).then( (value) => console.log('resolved with ' + value) )
.catch( (error) => console.log('error message: ', error.message) );
Nesting
When you have nested promises within then callbacks, make sure you always return the value returned by the inner promise as the return value of the outer then callback.
So in your case do this:
return creditMemo.findById( ....
//^^^^
For the same reason you need to do:
return ubiqInvoice.findById( ....
//^^^^
It would lead to far for this question/answer, but it is best practice to avoid nesting promise then calls all together. Instead of calling then on a nested promise, just return the promise without the then call, and apply that then call one level higher, so that you have a "flat" chain of then calls. This is just a matter of best practice, although it should also work like you have done it provided you always return the inner promises.
Position of the error handler
The error handler is placed at a wrong position; in fact you are using the comma operator. In short, you have this in your code:
new Promise(function (resolve, reject) {
// ... //
}), function (error) {
console.log("error: " + error);
}
The function after the comma is never executed as it is not an argument to a method call. It just sits behind a comma operator.
What you want is a catch method call on the new promise, and cascade the error in there so that Promise.all will also receive the rejection:
return new Promise(function (resolve, reject) {
// ... //
}).catch(function (error) {
console.log("error: " + error);
throw error; // cascade it
});
In server.writeAttachedCMinfo() the main things are :
to get rid of the new Promise() wrapper - it's not necessary - (Promise constructor anti-pattern).
to throw errors, rather than return them.
Also, because there's only one invId, there's effectively only one dbInvoice and it needn't be retrieved over and over from the database on each call of server.writeAttachedCMinfo(). In fact it shouldn't be retrieved over and over, otherwise each dbInvoice.save() may well overwrite earlier saves within the same overall transaction. It will be safer to accumulate all creditMemos and progressively decrement the certifiedInvoiceAmount in a single dbInvoice object, and evetually perform a single `dbInvoice.save().
server.writeAttachedCMinfo = function(dbInvoice, cmid) {
return creditMemo.findById(JSON.stringify(cmid).slice(1, -1))
.then(dbCreditMemo => {
if(parseInt(dbInvoice.certifiedInvoiceAmount) <= parseInt(dbCreditMemo.creditMemoAmount)) {
throw new Error('CMisbiggerThanInvoice');
// ^^^^^
}
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
/* all sorts of synchronous stuff */
return dbCreditMemo; // deliver dbCreditMemo via returned Promise
});
}
Now, in the caller :
ubiqInvoice.findById() can be called once.
perform checks on dbInvoice, and throw on failure.
pass dbInvoice, rather than invId to server.writeAttachedCMinfo() at each call.
all saving can be done here, not in server.writeAttachedCMinfo().
As a consequence, the caller subsumes some of the code that was in server.writeAttachedCMinfo() :
ubiqInvoice.findById(JSON.stringify(invId).slice(1, -1), 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos')
.then(dbInvoice => {
if(dbCreditMemo.isAssociated) {
throw new Error('CMisAssociated');
// ^^^^^
}
if(dbInvoice.gwCode === '100000000000'
|| dbInvoice.gwCode === '110000000000'
|| dbInvoice.gwCode === '111200000000'
|| dbInvoice.gwCode === '111100000000'
|| dbInvoice.gwCode === '111110000000'
) {
return Promise.all(receivedObjs.arrayCMsIds.map(cmid => {
return server.writeAttachedCMinfo(dbInvoice, cmid)
.catch(error => {
console.log(error);
return null;
});
}))
.then(dbCreditMemos => {
return Promise.all(dbCreditMemos.map(memo => {
return memo ? memo.save() : null;
}))
.then(() => dbInvoice.save())
.then(() => {
res.json(apiHelp.success(dbInvoice, 'success'));
});
});
} else {
throw new Error('wrongggwcode');
// ^^^^^
}
})
.catch(error => {
console.log('error: ' + error);
res.json(apiHelp.error(error, error));
});
The whole area around catching/handling errors arising from server.writeAttachedCMinfo() requires more thought. On the one hand, you might want successful sub-transactions to be saved and errors to be swallowed (as coded above) while on the other hand, you might want any single failure to cause nothing to be saved.
Another consideration is whether server.writeAttachedCMinfo() calls should be made sequentially, which would control the priority by which the sub-transactions access the credit balance. As it stands, with parallel requests, it's a bit of a free-for-all.
That addresses a bit more than the question asked for but hopefully it will be useful.

What happens if you don't resolve or reject a promise?

I have a scenario where I am returning a promise.
The promise is basically triggered by an ajax request.
On rejecting the promise it shows an error dialog that there is a server error.
What I want to do is when the response code is 401, I neither want to resolve the promise nor reject it (because it already shows the error dialog). I want to simply redirect to the login page.
My code looks something like this:
function makeRequest(ur, params) {
return new Promise(function (resolve, reject) {
fetch(url, params).then((response) => {
let status = response.status;
if (status >= 200 && status < 300) {
response.json().then((data) => {
resolve(data);
});
} else {
if (status === 401) {
redirectToLoginPage();
} else {
response.json().then((error) => {
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
reject({ status, error });
});
}
}
});
});
}
As you can see, if the status is 401, I am redirecting to the login page. The promise is neither resolved nor rejected.
Is this code OK, or is there any better way to accomplish this?
A promise is just an object with properties in Javascript. There's no magic to it. So failing to resolve or reject a promise just fails to ever change the state from "pending" to anything else. This doesn't cause any fundamental problem in Javascript because a promise is just a regular Javascript object. The promise will still get garbage collected (even if still pending) if no code keeps a reference to the promise.
The real consequence here is what does that mean to the consumer of the promise if its state is never changed? Any .then() or .catch() listeners for resolve or reject transitions will never get called. Most code that uses promises expects them to resolve or reject at some point in the future (that's why promises are used in the first place). If they don't, then that code generally never gets to finish its work.
Or if code is using await on the promise instead of .then(), then that function will just remain suspended forever on that await. The rest of the function will be dead code and will never execute.
It's possible that you could have some other code that finishes the work for that task and the promise is just abandoned without ever doing its thing. There's no internal problem in Javascript if you do it that way, but it is not how promises were designed to work and is generally not how the consumer of promises expect them to work.
As you can see if the status is 401, I am redirecting to login page.
Promise is neither resolved nor rejected.
Is this code OK? Or is there any better way to accomplish this.
In this particular case, it's all OK and a redirect is a somewhat special and unique case. A redirect to a new browser page will completely clear the current page state (including all Javascript state) so it's perfectly fine to take a shortcut with the redirect and just leave other things unresolved. The system will completely reinitialize your Javascript state when the new page starts to load so any promises that were still pending will get cleaned up.
I think the "what happens if we don't resolve reject" has been answered fine - it's your choice whether to add a .then or a .catch.
However, Is this code OK? Or is there any better way to accomplish this. I would say there are two things:
You are wrapping a Promise in new Promise when it is not necessary and the fetch call can fail, you should act on that so that your calling method doesn't sit and wait for a Promise which will never be resolved.
Here's an example (I think this should work for your business logic, not 100% sure):
const constants = {
SERVER_ERROR: "500 Server Error"
};
function makeRequest(url,params) {
// fetch already returns a Promise itself
return fetch(url,params)
.then((response) => {
let status = response.status;
// If status is forbidden, redirect to Login & return nothing,
// indicating the end of the Promise chain
if(status === 401) {
redirectToLoginPage();
return;
}
// If status is success, return a JSON Promise
if(status >= 200 && status < 300) {
return response.json();
}
// If status is a failure, get the JSON Promise,
// map the message & status, then Reject the promise
return response.json()
.then(json => {
if (!json.message) {
json.message = constants.SERVER_ERROR;
}
return Promise.reject({status, error: json.message});
})
});
}
// This can now be used as:
makeRequest("http://example", {})
.then(json => {
if(typeof json === "undefined") {
// Redirect request occurred
}
console.log("Success:", json);
})
.catch(error => {
console.log("Error:", error.status, error.message);
})
By contrast, calling your code using:
makeRequest("http://example", {})
.then(info => console.log("info", info))
.catch(err => console.log("error", err));
Will not log anything because the call to http://example will fail, but the catch handler will never execute.
As others stated it's true that it's not really an issue if you don't resolve/reject a promise. Anyway I would solve your problem a bit different:
function makeRequest(ur,params) {
return new Promise(function(resolve,reject) {
fetch(url,params)
.then((response) => {
let status = response.status;
if (status >= 200 && status < 300) {
response.json().then((data) => {
resolve(data);
})
}
else {
reject(response);
}
})
});
}
makeRequest().then(function success(data) {
//...
}, function error(response) {
if (response.status === 401) {
redirectToLoginPage();
}
else {
response.json().then((error) => {
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
//do sth. with error
});
}
});
That means I would reject every bad response state and then handle this in your error handler of your makeRequest.
It works and isn't really a problem, except when a caller of makeRequest expects of promise to fulfil. So, you're breaking the contract there.
Instead, you could defer the promise, or (in this case) reject with status code/error.
The ECMAScript spec explains the purpose of promises and new Promise():
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
25.6.3.1 Promise ( executor )
NOTE The executor argument must be a function object. It is called for initiating and reporting completion of the possibly deferred action represented by this Promise object.
You should use promises to obtain future values. Furthermore, to keep your code concise and direct, you should only use promises to obtain future values, and not to do other things.
Since you’ve also mixed program control flow (redirection logic) into your promise’s “executor” logic, your promise is no longer “a placeholder for the results of a computation;” rather, it’s now a little JavaScript program masquerading as a promise.
So, instead of wrapping this JavaScript program inside a new Promise(), I recommend just writing it like a normal JavaScript program:
async function makeRequest(url, params) {
let response = await fetch(url, params);
let { status } = response;
if (status >= 200 && status < 300) {
let data = await response.json();
successLogic(data);
} else if (status === 401) {
redirectToLoginPage();
} else {
let error = await response.json()
if (!error.message) {
error.message = constants.SERVER_ERROR;
}
errorLogic({ status, error });
}
}

Do I always need catch() at the end even if I use a reject callback in all then-ables?

I am putting catches at the end, but they are returning empty object in one particular instance at least. Is a catch necessary for anything unbeknownst, or is it just screwing me up?
$( document).ready(function(){
app.callAPI()//a chainable a RSVP wrapper around a jquery call, with its own success() fail() passing forward to the wrapper, so it will either be a resolved or rejected thenable to which is now going to be chained
.then(
function(env) {
//set the property you needed now
app.someSpecialEnvObj = env;
},
function(rejectMessage){
console.log('call.API() cant set some special env object..');
console.log(rejectMessage);
}
)
.catch(
function(rejectMessage){
if(rejectMessage){
//a just incase, DOES IT HAVE VALUE, for somebody that may have not done their homework in the parent calls?
console.log('you have some kind of legitimate error, maybe even in callAPI() that is not part of any problems inside them. you may have forgotton handle something at an early state, your so lucky this is here!)
} else {
console.log('can this, and or will this ever run. i.e., is there any value to it, when the necessity to already be logging is being handled in each and every then already, guaranteeing that we WONT be missing ANYTHING')
}
}
);
});
Is it wrong? or is there some kind of use for it, even when I still use an error/reject handler on all usages of .then(resolve, reject) methods in all parent chained then-ables?
EDIT: Better code example, I hope. I think I might be still be using some kind of anti-pattern in the naming, I rejectMessage in my e.g., it's the jqXhr object right?
So maybe I should be naming them exactly that or what? i.e. jqXhr? By the way, the reason I like to reject it on the spot inside each then(), if there was an error, is because this way I can copiously log each individual call, if there was a problem specifically there, that way I don't have to track anything down. Micro-logging, because I can.
Promises are helping opening up the world of debugging this way.
Here's the three examples I have tried. I prefer method1, and method2, and by no means am I going back to method3, which is where I started off in the promise land.
//method 1
app.rsvpAjax = function (){
var async,
promise = new window.RSVP.Promise(function(resolve, reject){
async = $.extend( true, {},app.ajax, {
success: function(returnData) {
resolve(returnData);
},
error: function(jqXhr, textStatus, errorThrown){
console.log('async error');
console.log({jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown});
reject({ jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown}); //source of promise catch data believe
}
});
$.ajax(async); //make the call using jquery's ajax, passing it our reconstructed object, each and every time
});
return promise;
};
app.callAPI = function () {
var promise =app.rsvpAjax();
if ( !app.ajax.url ) {
console.log("need ajax url");
promise.reject(); //throw (reject now)
}
return promise;
};
//method 2
app.ajaxPromise = function(){
var promise, url = app.ajax.url;
var coreObj = { //our XMLHttpRequestwrapper object
ajax : function (method, url, args) { // Method that performs the ajax request
promise = window.RSVP.Promise( function (resolve, reject) { // Creating a promise
var client = new XMLHttpRequest(), // Instantiates the XMLHttpRequest
uri = url;
uri = url;
if (args && (method === 'POST' || method === 'PUT')) {
uri += '?';
var argcount = 0;
for (var key in args) {
if (args.hasOwnProperty(key)) {
if (argcount++) {
uri += '&';
}
uri += encodeURIComponent(key) + '=' + encodeURIComponent(args[key]);
}
}
}
client.open(method, uri);
client.send();
client.onload = function () {
if (this.status == 200) {
resolve(this.response); // Performs the function "resolve" when this.status is equal to 200
}
else {
reject(this.statusText); // Performs the function "reject" when this.status is different than 200
}
};
client.onerror = function () {
reject(this.statusText);
};
});
return promise; // Return the promise
}
};
// Adapter pattern
return {
'get' : function(args) {
return coreObj.ajax('GET', url, args);
},
'post' : function(args) {
return coreObj.ajax('POST', url, args);
},
'put' : function(args) {
return coreObj.ajax('PUT', url, args);
},
'delete' : function(args) {
return coreObj.ajax('DELETE', url, args);
}
};
};
app.callAPI = function () {
var async, callback;
async =app.ajaxPromise() ; //app.ajaxPromise() is what creates the RSVP PROMISE HERE<
if(app.ajax.type === 'GET'){async = async.get();}
else if(app.ajax.type === 'POST') {async = async.post();}
else if(app.ajax.type === 'PUT'){async = async.put();}
else if(app.ajax.type === 'DELETE'){ async = async.delete();}
callback = {
success: function (data) {
return JSON.parse(data);
},
error: function (reason) {
console.log('something went wrong here');
console.log(reason);
}
};
async = async.then(callback.success)
.catch(callback.error);
return async;
};
//method 3 using old school jquery deferreds
app.callAPI = function () {
//use $.Deferred instead of RSVP
async = $.ajax( app.ajax) //run the ajax call now
.then(
function (asyncData) { //call has been completed, do something now
return asyncData; //modify data if needed, then return, sweet success
},
function(rejectMessage) { //call failed miserably, log this thing
console.log('Unexpected error inside the callApi. There was a fail in the $.Deferred ajax call');
return rejectMessage;
}
);
return async;
};
I also run this somewhere onready as another backup.
window.RSVP.on('error', function(error) {
window.console.assert(false, error);
var response;
if(error.jqXhr){
response = error.jqXhr;
} else {
//response = error;
response = 'is this working yet?';
}
console.log('rsvp_on_error_report')
console.log(response);
});
Edit error examples
//one weird error I can't understand, an empty string("")?
{
"jqXhr": {
"responseText": {
"readyState": 0,
"responseText": "",
"status": 0,
"statusText": "error"
},
"statusText": "error",
"status": 0
},
"textStatus": "error",
"errorThrown": "\"\""
}
//another wierd one, but this one comes from a different stream, the RSVP.on('error') function
{
"readyState": 0,
"responseText": "",
"status": 0,
"statusText": "error"
}
I am putting catches at the end
That's the typical position for them - you handle all errors that were occurring somewhere in the chain. It's important not to forget to handle errors at all, and having a catch-all in the end is the recommended practise.
even if I use onreject handlers in all .then(…) calls?
That's a bit odd. Usually all errors are handled in a central location (the catch in the end), but of course if you want you can handle them anywhere and then continue with the chain.
Just make sure to understand the difference between an onreject handler in a then and in a catch, and you can use them freely. Still, the catch in the end is recommended to catch errors in the then callbacks themselves.
they are returning empty object in one particular instance atleast.
Then the promise screwed up - it should never reject without a reason. Seems to be caused be the
if ( !app.ajax.url ) {
console.log("need ajax url");
promise.reject();
}
in your code that should have been a
if (!app.ajax.url)
return Promise.reject("need ajax url");
Is a catch necessary for anything unbeknownst?
Not really. The problem is that catch is usually a catch-all, even catching unexpected exceptions. So if you can distinguish them, what would you do with the unexpected ones?
Usually you'd set up some kind of global unhandled rejection handler for those, so that you do not have to make sure to handle them manually at the end of every promise chain.
I think the general question deserves a simple answer without the example.
The pedantic technical answer is 'no', because .catch(e) is equivalent to .then(null, e).
However (and this is a big "however"), unless you actually pass in null, you'll be passing in something that can fail when it runs (like a coding error), and you'll need a subsequent catch to catch that since the sibling rejection handler by design wont catch it:
.then(onSuccess, onFailure); // onFailure wont catch onSuccess failing!!
If this is the tail of a chain, then (even coding) errors in onSuccess are swallowed up forever. So don't do that.
So the real answer is yes, unless you're returning the chain, in which case no.
All chains should be terminated, but if your code is only a part of a larger chain that the caller will add to after your call returns, then it is up to the caller to terminate it properly (unless your function is designed to never fail).
The rule I follow is: All chains must either be returned or terminated (with a catch).
If I remember correctly, catch will fire when your promise is rejected. Since you have the fail callback attached, your catch will not fire unless you call reject function in either your fail or success callback.
In other words, catch block is catching rejection in your then method.

Categories

Resources