Here is my sample code.
Orchestrator calls the worker with couple of inputs first, upon getting the response, it has to validate whether the response was satisfactory or not.
If satisfactory, just return to the caller.
If not, again, call the same worker or may be different worker with slightly different input and follow the flow.
Here, although, my code calls cb() after the first worker call, it's also going to the second then and errors out "response" is undefined etc..
I could add an extra condition to check whether the 1st response was satisfactory, like need2ndworkercall && validate(response) in 2nd then and get away with it. But wondering what is the right way of dealing with this problem. Appreciate any feedback.
function orchestrateSomething(input, cb){
doSomething(input.a, input.b)
.then(response=>{
if(validate(response)){
cb(buildResultObj(response));
}
else{
return doSomething(input.a)
}
})
.then(response=>{
if(validate(response)){
cb(buildResultObj(response));
}
else{
cb(null,{});
}
})
.catch(error=>cb(error));
}
return value from function and .then(). Also cb function should call passed function which returns value or evaluate parameters and return value passed
function orchestrateSomething(input, cb){
return doSomething(input.a, input.b)
.then(response=>{
if(validate(response)){
return cb(buildResultObj(response));
}
else{
return doSomething(input.a)
}
})
.then(response=>{
if(validate(response)){
return cb(buildResultObj(response));
}
else{
return cb(null,{});
}
})
.catch(error=>cb(error));
}
orchestrateSomething(input, cb) // where `cb` calls function or values passed
.then(function(results) {
console.log(results)
})
.catch(function(err) {
console.log(err)
});
It is possible break the promise chain via simple throw. The trick is to handle it properly on the catch call:
doPromise(...)
.then(...)
.then(result => {
if(condition) {
throw result
}
else {
return doPromise()
}
})
.then(...)
.catch(result => {
if(result instanceof Error) {
// handle error result
}
else {
// handle desired result
}
})
Here's the simpliest demo of such approach: http://plnkr.co/edit/H7K5UsZIueUY5LdTZH2S?p=preview
By the way, if you can generalize then processing function, it becomes possible to make a recursive call:
processCB = (result) => {
if(condition) {
throw result
}
else {
return doPromise()
}
}
catchCB = (result) => {
if(result instanceof Error) {
// handle error result
}
else {
// handle desired result
}
}
doProcess = () => doPromise()
.then(processCB)
.catch(catchCB)
And here's the demo for the second piece: http://plnkr.co/edit/DF28KgBOHnjopPaQtjPl?p=preview
Related
Sometimes there is a need to call a function and handle the exception directly (with a try/catch), and other times there it is preferable to call the same function and receive a null or false if it would otherwise fail.
This idea or use-case is similar to Rails in ActiveRecord where you have a choice of calling User.find(1) or User.find!(1) — the former will return nil if not found and the latter will raise an exception.
Question: I am the author of these functions so I can design this in any way. Is there a prescribed pattern in javascript for this idea?
One idea is to just mimic what is done in ActiveRecord:
// returns API data or bubbles-up exception
function getDataFromAPI() {
const response = callApi(); // function throws error if not 200
return response.data;
}
// returns API data or null
function getDataFromAPI_() {
try {
return getDataFromAPI();
} catch(e) {
return null;
}
}
Maybe another way is to create a generic wrapper for the try/catch, though this example doesn't account for arguments:
// returns API data, but will raise an exception if
// `response` has no `data` property...
function getDataFromAPI() {
const response = callApi();
return response.data;
}
// return function or null
function nullIfError(func) {
try {
return func();
} catch(e) {
return null;
}
}
nullIfError(getDataFromAPI)
Or, add a parameter to conditially change it the behavior, but I don't like that it's always wrapped in a try/catch...
function getDataFromAPI(throwError = true) {
try {
const response = callApi();
return response.data;
} catch(e) {
if (throwError) throw(e);
else return null;
}
}
I've done whole-day research on how I can get result of each promise in a Promise.map and use it as input in the next iteration during loop of that same Promise.map. I strictly need to do this approach because am using this logic in a database operation that must be ATOMIC and in case of any promise rejection all the previous transactions have to be rolled back.
NOTE: I have used promise.each and it works well only that it does not allow me to associate the individual promises and roll back all if one fails. So Promise.map seem to be the best solution when each promise is carefully resolved and value returned without causing Error: Transaction query already complete in the next loop. Here is the logic with knex:
var obj={};
knex.transaction(function(trx) {
return Promise.map(array, function(item) {
return trx.insert(item).into('table')
.then(returnedFields => {
//do some validation/operation and return the result or the returnedFields themselves as input in the next loop.
//[START EDIT: this responds to comment by # Mikael Lepistö for clarity]
//update obj here to be used in next loop
//[END EDIT]
});
}, {concurrency: 1});
})
.then(function(inserts) {
console.log(inserts.length + 'Items saved.');
})
.catch(function(error) {
console.error(error);
})
Check this out:
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You can check out async.js library https://caolan.github.io/async/docs.html
Here's what you can do
knex.transaction(function(trx) {
return async.mapValuesSeries(array, (item) => {
return trx.insert(item).into('table')
.then(returnedFields => {
//do some validation/operation and return the result or the returnedFields themselves as input in the next loop.
});
}, (error, inserts) => {
if (err) {
console.error(error);
}
console.log(inserts.length + 'Items saved.');
});
});
Thank you everyone who posted great/greater insights. However it has turned out that Promise.each works well with knex and I was making a mistake of calling commit inside then of some individual promises during the loop hence causing Error: Transaction query already complete in next transaction attempt of the subsequent loop. Reason: There was no need calling commit withing the knex transaction context/block since it's automatically triggered in that context.
To note in the answer below:
Using Knex with Promise.each requires you to listen to possible rejection inside thenblock of each promise and with a try/catch and in some cases explicitly reject else the subsequent promises/values will continue looping and that cannot make database atomic!!!
knex.transaction(function(trx) {
return Promise.map(array, function(item) {
return trx.insert(item).into('table')
.then(returnedFields => {
try{
//do some validation/operation and return the result or the returnedFields themselves as input in the next loop.
/* START Testing a case of rejection */
if (array.indexOf(item) === 3) {
//uncomment the code below to test
//throw new Error('BreakException');
}
/* END */
}catch(err){
fail=true;
}
}).then(val => {
if (fail) {
trx.rollback()//you can ignore this as is automatically triggered here - tested
return Promise.reject(new Error('Rejected'))
}
return val
})
.catch(err => {
fail = true
trx.rollback()//you can ignore this as is automatically triggered here - tested
return Promise.reject(new Error('Rejected'))
});
});
})
.then(function(inserts) {
console.log(inserts.length + 'Items saved.');
})
.catch(function(error) {
console.error(error);
})
I have 3 ajax calls that need to be run serially. My issue is how do I do this with $interval. Is there another way to accomplish this?
I have these calls.
var server1 = getDataFromServer1()
var server2 = getDataFromServer2UsingServer1Data(server1);
var server3 = getDataFromServer3UsingServer2Data(server2);
However I want to repeat them but be able to cancel them if I get a certain value. Won't $interval run getDataFromServer1() before I get data from getDataFromServer3UsingServer2Data since it won't wait. Whats the best way to accomplish this? Thanks.
I am not sure whether this is correct approach or not but you can try something like this:
function getDataFromServer() {
getDataFromServer1().then(function(res1) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return
}
getDataFromServer2UsingServer1Data(server1).then(function(res2) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return
}
getDataFromServer3UsingServer2Data(server2).then(function(res3) {
if (value === YOU_CERTAIN_VALUE) {
//do what you want
return;
}
//calling the function again.
getDataFromServer();
});
});
});
}
This can be done using promise.
You should return a promise from each of your server call like below,
function getDataFromServer1(){
return $http.get('your_url', {options});
}
Here you are return the result from the $http. $http will return a promise so that you can handle this promise while calling this function.
Now you can achieve your requirement like this,
getDataFromServer1().then(function(response){
if(response.data == someValue){
// if you condition satisfied then call next server call
getDataFromServer2UsingServer1Data().then(function(response){
if(some_condition){
getDataFromServer3UsingServer2Data().then(function(response) {
});
}
});
}
});
Chaining promises is the better way to handle errors and elegant when there is such dependencies.
function getDataFromServer1() {
return $http
.get('/some/url/in/server1')
.then(function(response) {
$scope.server1Data = response.data; // If this data is needed in scope
return response.data;
});
}
function getDataFromServer2WithServer1Data(server1Data) {
if(server1Data.condition === 'not met') {
return $q.reject('server1 condition not met');
}
return $http
.get('/some/url/in/server2')
.then(function(response) {
$scope.server2Data = response.data; // If this data is needed in scope
if(server2Data.condition === 'not met') {
throw 'server2 condition not met'; // other way to abort the chain, it reaches the final catch block
}
return response.data;
});
}
function getDataFromServer3WithServer2Data(server2Data) {
return $http
.get('/some/url/in/server3')
.then(function(response) {
$scope.server3Data = response.data; // If this data is needed in scope
return response.data;
});
}
getDataFromServer1()
.then(getDataFromServer2WithServer1Data)
.then(getDataFromServer3WithServer2Data)
.catch(function(rejectReason){
console.log('error occured', rejectReason);
});
This blog explains more about this scenario, would recommend to go through it.
it is a common pattern that we cascade across a list of sources of data with the first success breaking the chain like this:
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
et cetera. if the getDataN() functions are asynchronous, however, it leads us to 'callback hell':
var data;
getData1(function() {
getData2(function () {
getData3(function () { alert('not found'); })
})
});
where the implementations may look something like:
function getData1(callback) {
$.ajax({
url: '/my/url/1/',
success: function(ret) { data = ret },
error: callback
});
}
...with promises I would expect to write something like this:
$.when(getData1())
.then(function (x) { data = x; })
.fail(function () { return getData2(); })
.then(function (x) { data = x; })
.fail(function () { return getData3(); })
.then(function (x) { data = x; });
where the second .then actually refers to the return value of the first .fail, which is itself a promise, and which I understood was chained in as the input to the succeeding chain step.
clearly I'm wrong but what is the correct way to write this?
In most promise libs, you could chain .fail() or .catch() as in #mido22's answer, but jQuery's .fail() doesn't "handle" an error as such. It is guaranteed always to pass on the input promise (with unaltered state), which would not allow the required "break" of the cascade if/when success happens.
The only jQuery Promise method that can return a promise with a different state (or different value/reason) is .then().
Therefore you could write a chain which continues on error by specifying the next step as a then's error handler at each stage.
function getDataUntilAsyncSuccess() {
return $.Deferred().reject()
.then(null, getData1)
.then(null, getData2)
.then(null, getData3);
}
//The nulls ensure that success at any stage will pass straight through to the first non-null success handler.
getDataUntilAsyncSuccess().then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
But in practice, you might more typically create an array of functions or data objects which are invoked in turn with the help of Array method .reduce().
For example :
var fns = [
getData1,
getData2,
getData3,
getData4,
getData5
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, fn) {
return promise.then(null, fn);
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(fns).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
Or, as is probably a better solution here :
var urls = [
'/path/1/',
'/path/2/',
'/path/3/',
'/path/4/',
'/path/5/'
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, url) {
return promise.then(null, function() {
return getData(url);// call a generalised `getData()` function that accepts a URL.
});
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(urls).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
As a beginner, stumbling across the same problem, I just realized how much simpler this has become with async and await:
The synchronous pattern
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
can now easily be applied to asynchronous code:
let data = await getData1();
if (!data) data = await getData2();
if (!data) data = await getData3();
Just remember to add an async to the function that this code is used in.
I a promise in such fashion,
function getMode(){
var deferred = Promise.defer();
checkIf('A')
.then(function(bool){
if(bool){
deferred.resolve('A');
}else{
return checkIf('B');
}
}).then(function(bool){
if(bool){
deferred.resolve('B');
}else{
return checkIf('C');
}
}).then(function(bool){
if(bool){
deferred.resolve('C');
}else{
deferred.reject();
}
});
return deferred.promise;
}
checkIf returns a promise, and yes checkIf cannot be modified.
How do I break out of the chain at the first match? (any way other than explicitly throwing error?)
Any way other than explicitly throwing error?
You may need to throw something, but it does not have to be an error.
Most promise implementations have method catch accepting the first argument as error type (but not all, and not ES6 promise), it would be helpful under this situation:
function BreakSignal() { }
getPromise()
.then(function () {
throw new BreakSignal();
})
.then(function () {
// Something to skip.
})
.catch(BreakSignal, function () { })
.then(function () {
// Continue with other works.
});
I add the ability to break in the recent implementation of my own promise library. And if you were using ThenFail (as you would probably not), you can write something like this:
getPromise()
.then(function () {
Promise.break;
})
.then(function () {
// Something to skip.
})
.enclose()
.then(function () {
// Continue with other works.
});
You can use
return { then: function() {} };
.then(function(bool){
if(bool){
deferred.resolve('A');
return { then: function() {} }; // end/break the chain
}else{
return checkIf('B');
}
})
The return statement returns a "then-able", only that the then method does nothing.
When returned from a function in then(), the then() will try to get the result from the thenable.
The then-able's "then" takes a callback but that will never be called in this case. So the "then()" returns, and the callback for the rest of the chain does not happen.
I think you don't want a chain here. In a synchronous fashion, you'd have written
function getMode(){
if (checkIf('A')) {
return 'A';
} else {
if (checkIf('B')) {
return 'B';
} else {
if (checkIf('C')) {
return 'C';
} else {
throw new Error();
}
}
}
}
and this is how it should be translated to promises:
function getMode(){
checkIf('A').then(function(bool) {
if (bool)
return 'A';
return checkIf('B').then(function(bool) {
if (bool)
return 'B';
return checkIf('C').then(function(bool) {
if (bool)
return 'C';
throw new Error();
});
});
});
}
There is no if else-flattening in promises.
I would just use coroutines/spawns, this leads to much simpler code:
function* getMode(){
if(yield checkIf('A'))
return 'A';
if(yield checkIf('B'))
return 'B';
if(yield checkIf('C'))
return 'C';
throw undefined; // don't actually throw or reject with non `Error`s in production
}
If you don't have generators then there's always traceur or 6to5.
You could create a firstSucceeding function that would either return the value of the first succeeded operation or throw a NonSucceedingError.
I've used ES6 promises, but you can adapt the algorithm to support the promise interface of your choice.
function checkIf(val) {
console.log('checkIf called with', val);
return new Promise(function (resolve, reject) {
setTimeout(resolve.bind(null, [val, val === 'B']), 0);
});
}
var firstSucceeding = (function () {
return function (alternatives, succeeded) {
var failedPromise = Promise.reject(NoneSucceededError());
return (alternatives || []).reduce(function (promise, alternative) {
return promise.then(function (result) {
if (succeeded(result)) return result;
else return alternative();
}, alternative);
}, failedPromise).then(function (result) {
if (!succeeded(result)) throw NoneSucceededError();
return result;
});
}
function NoneSucceededError() {
var error = new Error('None succeeded');
error.name = 'NoneSucceededError';
return error;
}
})();
function getMode() {
return firstSucceeding([
checkIf.bind(null, 'A'),
checkIf.bind(null, 'B'),
checkIf.bind(null, 'C')
], function (result) {
return result[1] === true;
});
}
getMode().then(function (result) {
console.log('res', result);
}, function (err) { console.log('err', err); });
i like a lot of the answers posted so far that mitigate what the q readme calls the "pyramid of doom". for the sake of discussion, i'll add the pattern that i plunked out before searching around to see what other people are doing. i wrote a function like
var null_wrap = function (fn) {
return function () {
var i;
for (i = 0; i < arguments.length; i += 1) {
if (arguments[i] === null) {
return null;
}
}
return fn.apply(null, arguments);
};
};
and i did something totally analogous to #vilicvane's answer, except rather than throw new BreakSignal(), i'd written return null, and wrapped all subsequent .then callbacks in null_wrap like
then(null_wrap(function (res) { /* do things */ }))
i think this is a good answer b/c it avoids lots of indentation and b/c the OP specifically asked for a solution that doesn't throw. that said, i may go back and use something more like what #vilicvane did b/c some library's promises might return null to indicate something other than "break the chain", and that could be confusing.
this is more a call for more comments/answers than a "this is definitely the way to do it" answer.
Probably coming late the party here, but I recently posted an answer using generators and the co library that would answer this question (see solution 2):
https://stackoverflow.com/a/43166487/1337392
The code would be something like:
const requestHandler = function*() {
const survey = yield Survey.findOne({
_id: "bananasId"
});
if (survey !== null) {
console.log("use HTTP PUT instead!");
return;
}
try {
//saving empty object for demonstration purposes
yield(new Survey({}).save());
console.log("Saved Successfully !");
return;
}
catch (error) {
console.log(`Failed to save with error: ${error}`);
return;
}
};
co(requestHandler)
.then(() => {
console.log("finished!");
})
.catch(console.log);
You would pretty much write synchronous code that would be in reality asynchronous !
Hope it helps!
Try to use libs like thisone:
https://www.npmjs.com/package/promise-chain-break
db.getData()
.then(pb((data) => {
if (!data.someCheck()) {
tellSomeone();
// All other '.then' calls will be skiped
return pb.BREAK;
}
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
console.error(error);
});