Breaking promise chain after the first error - javascript

I have two consecutive asynchronous operations, the problem is that when the first is an error, the second operation is still being executed:
File.convertToBase64(file.files[0])
.then(function (code) {
let params = {
csv: code
};
return new Api().createFromCSV(params);
})
.catch(function (error) {
dispatch(showError(error));
return false; // doesn't work
})
.then(function (response) {
dispatch(showSuccess('File was imported!'));
})
.catch(function (error) {
dispatch(showError(error));
});
So, if the first catch gets called, I don't want to execute the .then after it, I want the chain execution stopped. How can I deal with this?

Related

Will this catch block ever execute?

So is it possible that we can go into the second error block?
Given that we have some promise rejection or errors that happen in the logic section
somePromise
.then(function(data) {
//some logic
return something;
})
.then((data) => {
// more logic
}, function(err) {
// first error block
})
.catch(function(err) {
// second error block
});
Yes, it's possible - if the second function passed to the .then throws an error (or returns a rejected Promise), the error will be passed down to the next .catch:
Promise.resolve()
.then(function(data) {
//some logic
throw new Error();
})
.then((data) => {
// more logic
}, function(err) {
// first error block
console.log('Handling first error');
return err.somePropertyThatDoesNotExist.text;
})
.catch(function(err) {
// second error block
console.log('Handling second error')
});
As comment notes, the catch will also run if the first function passed to the second .then throws an error:
Promise.resolve()
.then(function(data) {
//some logic
return 'Foo';
})
.then((data) => {
// more logic
throw new Error();
}, function(err) {
// first error block
console.log('Handling error in then');
})
.catch(function(err) {
// second error block
console.log('Handling error in catch')
});
Yes the callback of .catch will be called if an error occurred in either the // more logic or the // first error block block.
// first error block won't be called for errors happening in // more logic
Yes it will catch the error in first block here is the below example for the same.
var promise1 = new Promise(function(resolve, reject) {
throw 'Ohh Nooo!';
});
promise1.then((data) => {
// more logic
}, function(err) {
// first error block
console.log("Catch Error Block 1");
}).catch(function(error) {
console.log(error);
});
// expected output: Uh-oh!

Uncaught (in promise)

I know the problem is usual. I'm using es6 promises, and I have multiple layers.
On runtime, when I don't catch a promise, I have Uncaught (in promise) in my console. But the fact is that I do catch it lower in my code.
Fast simplified example :
LoginApi.js
var loginDaoCall = loginDao.login(username, password);
loginDaoCall
.then(function (res) {
store.dispatch(loginSuccess());
log.log("[loginApi.login] END");
})
.catch(function (err) {
store.dispatch(loginFail());
errorUtils.dispatchErrorWithTimeout(errorLogin);
log.log(err);
});
return loginDaoCall;
loginContainer.js
loginApi.login(user, password).then(() => {
// Change here instead of in render so the user can go back to login page
this.props.history.push(baseUrlRouter + "test");
}); // <- Error here cause I don't CATCH the promise, but I do catch it in my loginapi.js
I know that I could catch doing nothing, but eh. I could also do the history push thing in my API layer, but it is not its responsibility.
How can I avoid the error in my console? Is there a way? I'm even thinking about leaving it like this.
Your problem is that you were returning the rejected loginDaoCall, not the promise where the error was already handled. loginApi.login(user, password) did indeed return a rejected promise, and even while that was handled in another branch, the promise returned by the further .then() does also get rejected and was not handled.
You might want to do something like
// LoginApi.js
return loginDao.login(username, password).then(function (res) {
store.dispatch(loginSuccess());
log.log("[loginApi.login] END");
return true;
}, function (err) {
store.dispatch(loginFail());
errorUtils.dispatchErrorWithTimeout(errorLogin);
log.log(err);
return false;
}); // never supposed to reject
// loginContainer.js
loginApi.login(user, password).then(success => {
if (success) {
// Change here instead of in render so the user can go back to login page
this.props.history.push(baseUrlRouter + "test");
}
});
It sounds like you have an error in your catch block. When the error is thrown there is no 2nd catch block to catch the error in the 1st catch block.
To fix it ...
.then(function (res) {
// some code that throws an error
})
.catch(function (err) {
// some code that throws an error
})
.catch(function (err) {
// This will fix your error since you are now handling the error thrown by your first catch block
console.log(err.message)
});

chaining promise (bluebird)

I am using nodeJs and bluebird. i have methode to check for parameters validity, so i create a module where i have (see code below) and all of them are promises.
exports.validateSpeciality = validateSpeciality;
exports.validateGovernorate = validateGovernorate;
exports.validateCities = validateCities;
In my controller (see code below) i always get the first promise result in the then containing "res.send(results)"
validator
.validateSpeciality(speciality)
.then(validator.validateGovernorate(governorate))
.then(validator.validateCities(governorate, cities))
.then(Doctor.searchBySpecialityAndByCities(speciality, cities))
.then(function (results) {
console.log(results);
res.send(results);
})
.catch(function (error) {
console.log(error);
res.status(400).send(error);
})
Can someone explain to me why it is not working in this way? even if one of the promise is rejected it always execute the last then and don't go to catch.
Currently in your Promise chain, you don't use the results of the previous function calls. You run the next promise, when the previous was not resolved. So, even one of the functions: validateGovernorate, validateCities, searchBySpecialityAndByCities rejected with error, the final then will be called anycase.
To fix that, run the next function, when the previous promise is resolved:
validator
.validateSpeciality(speciality)
.then(function() {
return validator.validateGovernorate(governorate);
})
.then(function() {
return validator.validateCities(governorate, cities);
});
.then(function() {
return Doctor.searchBySpecialityAndByCities(speciality, cities);
})
.then(function(results) {
console.log(results);
res.send(results);
})
.catch(function (error) {
console.log(error);
res.status(400).send(error);
});
Also, check that you haven't catch blocks in all the functions: validateGovernorate, validateCities, searchBySpecialityAndByCities. And if you have throw an error there or reject with error.
Check this article there is very good desribed how promises work.

Promise code are read twice

I use the following code to read json file and return a promise
I've two questions
return globAsync("folder/*.json").catch(function (err) {
throw new Error("Error read: " + err);
}).map(function (file) {
return fs.readFileAsync(file, 'utf8')
.then(function (res) {
console.log("test");
return JSON.parse(res);
},
function (err) {
throw new Error("Error :" + err);
}).then(function () {
console.log("test2");
});
});
I use the console log and I see that the console is printed twice
test
test
test2
test2
why its happening and how to avoid it ?
In the place I've put console.log("test2"); I need to invoke event
that the json parse is finished and still return outside the json object (to the caller), when I add the last then it doesn't work(the returned object is undefined),any idea how to do that right?
UPDATE I try like following which it doesn't work...
return globAsync("folder/*.json").catch(function (err) {
throw new Error("Error read: " + err);
}).map(function (file) {
return fs.readFileAsync(file, 'utf8')
.then(function (res) {
console.log("test");
JSON.parse(res); //data parse
}.catch(function (err) {
throw new Error("Error :" + err);
}
).then(function (data) {
obj.emit('ready');
return data;
}))
});
}
UPDATE2 I was able to solve it by simply add new return JSON.parse(res);
Now how should I solve the first issue which method called twice
Like #jaromandaX said, you probably got two *.json files. Try to print out the file name instead and it should become more obvious. In that case, .map is expected to be called twice, once for each file. Otherwise you aren't gonna be able to read and parse two files together.
If you want to get it to converge to a single point after all file reads and parses are complete, then you need to chain another .then after .map. eg.
return globAsync("folder/*.json")
.map(function(file) {
...
})
.then(function() {
obj.emit('ready');
});
EDIT To answer your question in comment. There are a few things you should keep in mind.
Throwing Error inside the promise chain will get caught by the promise and send it into the rejection flow. You may still throw an error if you are interested in getting custom error type or printing stack trace in a desirable way. But most people prefer return Promise.reject(error).
Any rejection in .map will send the promise chain into rejection flow.
Inside the rejection chain, if you want to continue down the rejection flow. You need to return Promise.reject(error), otherwise if you don't return a reject object, you can bring it back into resolve flow.
If you want to want to handle each error individually, you can do something like this:
return globAsync("folder/*.json")
.catch(function(error) {
// TODO: Handle error
return Promise.reject(error);
})
.map(function(file) {
return fs.readFileAsync(file, 'utf8')
.catch(function(error) {
// TODO: Handle error
return Promise.reject(error);
})
.then(function(res) {
return JSON.parse(res);
});
})
.then(function() {
obj.emit('ready');
});
If you want to handle once for glob and once for file read, then you have to get a bit more creative.
return globAsync("folder/*.json")
.catch(function(error) {
// TODO: Handle error
return Promise.reject(error);
})
.then(function(files) {
return Promise.resolve(files)
.map(function(file) {
return fs.readFileAsync(file, 'utf8');
})
.catch(function(error) {
// TODO: Handle error once for any read error
return Promise.reject(error);
})
.map(function(res) {
// Judging by your original code, you are not handling
// parser error, so I wrote this code to behave equivalent
// to your original. Otherwise chain parse immediate after
// readFileAsync.
return JSON.parse(res);
});
})
.then(function() {
obj.emit('ready');
});

How can you retry after an exception in Javascript when using promises?

I'm using the Bluebird promise library. I have a chain of promisified functions like the following:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.spread(function(response, data) {
return sendResponseAsync(response);
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// handle error here
});
Occasionally sendMessage will fail because, let's say, the server to respond to isn't available. I want the code to keep on trying to respond forever until it succeeds. You can't simply wrap the sendMessage in a catch because it doesn't actually throw an exception, I suppose, it calls the "error" function which, in this promisified code is the "catch" at the bottom. So there must be some way to "retry" send message in the "catch" section. The problem is that even if I retry in a loop in the "catch" I still have no way to jump up to the promise chain and execute the remaining promisified functions. How do I deal with this?
EDIT:
My retry for a HTTP post ended up looking like this:
function retry(func) {
return func()
.spread(function(httpResponse) {
if (httpResponse.statusCode != 200) {
Log.error("HTTP post returned error status: "+httpResponse.statusCode);
Sleep.sleep(5);
return retry(func);
}
})
.catch(function(err) {
Log.err("Unable to send response via HTTP");
Sleep.sleep(5);
return retry(func);
});
}
Here's a sample retry function (not yet tested):
function retry(maxRetries, fn) {
return fn().catch(function(err) {
if (maxRetries <= 0) {
throw err;
}
return retry(maxRetries - 1, fn);
});
}
The idea is that you can wrap a function that returns a promise with something that will catch and retry on error until running out of retries. So if you're going to retry sendResponseAsync:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.spread(function(response, data) {
return retry(3, function () { return sendResponseAsync(response); });
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// handle error here
});
Since the retry promise won't actually throw until all retries have been exhausted, your call chain can continue.
Edit:
Of course, you could always loop forever if you preferred:
function retryForever(fn) {
return fn().catch(function(err) {
return retryForever(fn);
});
}
Here is a small helper that acts like then but retries the function.
Promise.prototype.retry = function retry(onFulfilled, onRejected, n){
n = n || 3; // default to 3 retries
return this.then(function(result) {
return Promise.try(function(){
return onFulfilled(result); // guard against synchronous errors too
}).catch(function(err){
if(n <= 0) throw err;
return this.retry(onFulfilled, onRejected, n - 1);
}.bind(this)); // keep `this` value
}.bind(this), onRejected);
};
Which would let you write your code prettier like:
receiveMessageAsync(params)
.then(function(data)) {
return [data, handleMessageAsync(request)];
})
.spread(function(data, response) {
return [response, deleteMessageAsync(request)];
})
.retry(function(response, data) {
return sendResponseAsync(response); // will retry this 3 times
})
.then(function(data) {
return waitForMessage(data);
})
.catch (function(err) {
// I don't like catch alls :/ Consider using `.error` instead.
});
I just released https://github.com/zyklus/promise-repeat, which retries a promise until it either times out or a maximum number of attempts are hit. It allows you to write:
receiveMessageAsync(params)
...
.spread(retry(
function(response, data) {
return sendResponseAsync(response);
}
))
...

Categories

Resources