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.
Related
I'm new to Node/Express and am trying to use Promises to executive successive API calls to Apple's CloudKit JS API.
I'm unclear on how to put the functions in sequence and pass their respective return values from one function to the next.
Here's what I have so far:
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0])
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.')
}else{
return Promise.resolve(response.records[0])
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
Promise.reject(response.errors[0])
}else{
Promise.resolve()
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord(record)) //<-- This is the 2nd function above
Promise.resolve('Success!')
.catch(function(error){
Promise.reject(error)
})
}
I get an error near the end: .then(saveRecord(record)) and it says record isn't defined. I thought it would somehow get returned from the prior promise.
It seems like this should be simpler than I'm making it, but I'm rather confused. How do I get multiple Promises to chain together like this when each has different resolve/reject outcomes?
There are few issues in the code.
First: you have to pass function to .then() but you actually passes result of function invocation:
.then(saveRecord(record))
Besides saveRecord(record) technically may return a function so it's possible to have such a statement valid it does not seem your case. So you need just
.then(saveRecord)
Another issue is returning nothing from inside saveRecord and fetchUserRecord function as well.
And finally you don't need to return wrappers Promise.resolve from inside .then: you may return just transformed data and it will be passed forward through chaining.
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
return CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.');
}else{
return response.records[0];
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
return CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else{
return Promise.resolve();
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
return CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord) //<-- This is the 2nd function above
.catch(function(error){});
}
Don't forget returning transformed data or new promise. Otherwise undefined will be returned to next chained functions.
Since #skyboyer helped me figure out what was going on, I'll mark their answer as the correct one.
I had to tweak things a little since I needed to pass the returned values to subsequent functions in my promise chain. Here's where I ended up:
exports.startActivation = function(emailConfirmationCode){
return new Promise((resolve, reject) => {
CloudKit.container.setUpAuth()
.then(() => {
return fetchUserRecord(emailConfirmationCode)
})
.then((record) => {
resolve(saveRecord(record))
}).catch(function(error){
reject(error)
})
})
}
I want to return a zipcode before I call a 2nd service, so that -- from what I thought I know -- I can wrap in a promise, and then ask for the promise later. So I figured I would just place my 2nd service inside the promise of the 1st service. But chaining promises this way is not being friendly.
Angular factory is called up and inside the factory method:
var userGeoPromise = userService.getGeoposition().then(function (geoposition) {
vm.geoposition = geoposition;
return addressService.reverseGeocode(geoposition.coords);
}).then(function (data) {
vm.currentLocation = googleService.googleAddressComponentsToAddress(data.results[0]);
zipCodeCurrent = vm.currentLocation.zip;
});
Notice 2 things above:
I assigned the promise to var userGeoPromise
zipCodeCurrent is set which contains the zipcode
Promise Testing works fine:
userGeoPromise.then( function() {
console.log('should always show', zipCodeCurrent);
});
2nd service call:
userGeoPromise.then( function() {
var serviceBase = "http://localhost:2295/api/getservicezip/"+ zipCodeCurrent;
var serviceZipPromise = $http.get(serviceBase);
return serviceZipPromise.then(function (results) {
console.log('serviceZipPromise', results);
return results.data;
});
});
But now the site modal just spins when I put the serviceZipPromise.then... inside the other promise.
In a then callback you should return the result value, not assign it to zipCodeCurrent (or any other variable). You did this correctly in the first then callback, but you should apply the same principle in the second:
var userGeoPromise = userService.getGeoposition().then(function (geoposition) {
vm.geoposition = geoposition;
return addressService.reverseGeocode(geoposition.coords);
}).then(function (data) {
vm.currentLocation = googleService.googleAddressComponentsToAddress(data.results[0]);
return vm.currentLocation.zip; // *** return it
});
NB: I did not touch the assignments to the properties of vm, but normally you should avoid mutating variables which (apparently) exist outside of the scope of these callback functions.
The Promise test would look like this:
userGeoPromise.then( function(zipCodeCurrent) { // *** add the argument
console.log('should always show', zipCodeCurrent);
});
The 2nd service has a nested then call, which is something to avoid. Instead of calling a then on a nested, intermediate promise, return that promise, and apply the then on the main promise chain:
userGeoPromise.then( function(zipCodeCurrent) { // *** add the argument as in the test
var serviceBase = "http://localhost:2295/api/getservicezip/"+ zipCodeCurrent;
return $http.get(serviceBase); // *** return the promise
}).then( function (results) { // *** move the then-callback to the outer chain
console.log('serviceZipPromise', results);
return results.data;
}).catch( function (error) { // *** add error handling at the end of the chain
console.log('error occurred:', error);
});
Note how the nesting level is never more than 1.
Switch the order and add in 2 separate error handlers ( which is a good practice btw)
var serviceZipPromise = $http.get(serviceBase); // call up
return serviceZipPromise.then(function (results) {
console.log('should always show', zipCodeCurrent);
userGeoPromise.then(function () {
//console.log('serviceZipPromise', results);
console.log('inside servicezip ', zipCodeCurrent);
}, function (err) { // error from userGeoPromise
console.log(err);
});
return results.data; // THIS will return the data
}, function (err) { // outer error, this was switched
console.log(err);
});
This should not error, but for your real serviceBase to end up using the zip code, u might have to execute it a bit later
UPDATE answer for you
// created part of the api call
var xserviceBase = "http://localhost:2295"; // this looks to be your base
return userGeoPromise.then(function(){
return $http.get(xserviceBase + '/api/getserviceablezip/' + zipCodeCurrent).then(function(results){
return results.data;
})
});
Yes, I know that the 3 returns look a bit nasty, but it should work
How do i chain promises sequentially within for loop, i have seen lot of examples on google to do this but i couldn't implement for my case:
i have gone through this link for sequential chaining of Promises.
What I'm trying to acheive:
Promise1: login();
Promise2: sync();
sync function calls another service complete() for an array of elements. These array of elements must be done sequentially.
ServiceA.login().
then(function(response){
ServiceA.sync()
.then(function(response){
})
})
function sync(){
ServiceB.complete()
.then(function(){
var promises = [];
angular.forEach(response, function (value) {
// The below service call doSomething() must be done sequentially for each "value"
promises.push(doSomething(value));
});
$q.all(promises).then(function () {
});
});
})
}
How do I capture the error occuring in each Promise?
Update:
I have tried the approach suggested by #zaptree with the following code:
ServiceA.login()
.then(function(response){
// you must always return your promise
return ServiceA.sync()
})
// don't nest the .then make them flat like this
.then(function(response){
})
.catch(function(){
// if you made sure to always return your promises this catch will catch any errors throws in your promise chain including errors thrown by doSomething()
});
function sync(){
// you must always return your promise
return ServiceB.complete()
.then(function(){
var result = $q.when();
angular.forEach(response, function (value) {
result = result.then(doSomething(value)); // problem is here that doSomething function is being called before the first call it is resolved
// doSomething is a http call.
});
return result;
})
.then(function(){
// the array of promises has run sequentially and is completed
});
}
function doSomething(data){
return $http({
method: 'POST',
url: '/api/do',
data: data,
headers: {
"Content-Type": "application/json"
}
}).then(function (response) {
}, function (error) {
});
}
If the response in the near the for each loop has 2 values (valuea, valueb) in it, the code is behaving as follows:
1. calling doSomething(valuea)
2. calling doSomething(valueb) before the above promise is resolved.
Expected behaviour:
after the POST method has succesfully completed by the call doSOmething(valuea), then the another POST call should happend i.e., soSomething(valueb).
Here's what I came up with. You'll need to reduce the array into a single promise.
var results = [...];
var sequentialPromise = results.reduce(function(a, b) {
return a.then(function(){
return doSomething(b);
});
}, $q.resolve());
sequentialPromise.then(function(){...});
So here is an example on how you would do the sequential promises with Q, also some improvements on how to do your promises so you can properly catch errors thrown at any point in your promise chain. You must always make sure to return a promise on any method that uses them. Also avoid pyramid code by not nesting the .then to make your code cleaner:
ServiceA.login()
.then(function(response){
// you must always return your promise
return ServiceA.sync()
})
// don't nest the .then make them flat like this
.then(function(response){
})
.catch(function(){
// if you made sure to always return your promises this catch will catch any errors throws in your promise chain including errors thrown by doSomething()
});
function sync(){
// you must always return your promise
return ServiceB.complete()
.then(function(){
var result = $q.when();
angular.forEach(response, function (value) {
result = result.then(doSomething(value));
});
return result;
})
.then(function(){
// the array of promises has run sequentially and is completed
});
}
I made a "promisified" version of fs.readFile using Javascript's native Promise, which parses a JSON file and returns the object when resolving.
function readFileAsync(file, options) {
return new Promise(function (resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) {
reject(err);
} else {
var object = JSON.parse(data);
resolve(object);
}
});
});
}
I first tried chaining the promises myself, but for some reason first the districts.variable1 from promise 1 gets logged, then comparePersonalities is called in promise 3, which gives an error because user is still undefined, and then user.variable2 gets logged from promise 2.
var user, district;
readFileAsync(file1, 'utf8').then(function (data) {
districts = data;
console.log(districts.variable1);
}).then(readFileAsync(file2, 'utf8').then(function (data) {
user = data;
console.log(user.variable2);
})).then(function (result) {
comparePersonalities(districts, user);
}).catch(function (e) {
console.log(e);
});
I also tried an alternate approach using Promise.all but this still results in an incorrect ordering and the comparePersonalities failing.
Promise.all([readFileAsync(file1), readFileAsync(file2)]).then(function (first, second) {
districts = first;
user = second;
comparePersonalities(districts, user);
});
When logging the resolved objects within the promise everything seems to work well, I can't figure out why everything is eventually initialized but the last promise runs before the second promise has finished. What am I doing wrong in both the chained promise and in Promise.all?
Promise.all is much more appropriate for your use case. You made a mistake in the callback: the outer promise is resolved with an array of results (in the same order as the inner promises), so then(function (first, second) {...}) is incorrect. Try something like this
Promise.all([
readFileAsync(file1, 'utf8'),
readFileAsync(file2, 'utf8')
]).then(function (results) {
// note that "results" is an array of two values
var districts = results[0];
var user = results[1];
comparePersonalities(districts, user);
});
Promises are always resolved with only one value. That's really important, and actually simplifies a lot of things, since you always know how many elements to expect.
The first example
You've made a mistake, in which you are passing a Promise to the .then method, when in reality it always expects a function. Notice the snippet readFileAsync(file2, 'utf8') is nicely wrapped in an anonymous function.
var user, district;
readFileAsync(file1, 'utf8').then(function (data) {
districts = data;
console.log(districts.variable1);
})
.then(function () { return readFileAsync(file2, 'utf8') })
.then(function (data) {
user = data;
console.log(user.variable2);
}).then(function (result) {
comparePersonalities(districts, user);
}).catch(function (e) {
console.log(e);
});
The second example
But, in that case you're probably better off using the Promise.all method, as the promises get resolved and nicely returned in your next funciton call.
The problem in your snippet is that promises always resolve one single object, and in the case of Promise.all, you should be expecting one single array. You can actually use es6 destructuring to simplify your code:
Promise.all([readFileAsync(file1), readFileAsync(file2)])
.then(function ([districts, user]) {
comparePersonalities(districts, user);
});
You have to return the Promises each time for chaining:
readFileAsync(file1, 'utf8').then(function(data) {
districts = data;
console.log(districts.variable1);
return readFileAsync(file2, 'utf8');
}).then(function(data) {
user = data;
console.log(user.variable2);
comparePersonalities(districts, user);
}).catch(function(e) {
console.log(e);
});
comparePersonalities(districts, user) will only work if your variable districts is declared at a higher scope. Otherwise it will be undefined once you reach this function.
Let's say I have some code that looks like this:
var doSomething = function(parameter){
//send some data to the other function
return when.promise(function(resolveCallback, rejectCallback) {
var other = doAnotherThing(parameter);
//how do I check and make sure that other has resolved
//go out and get more information after the above resolves and display
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
How do I ensure that var other has completely resolved before finishing and resolving the first doSomething() function? I'm still wrapping my head around Nodes Async characteristic
I really didn't know how else to explain this, so I hope this makes sense! Any help is greatly appreciated
EDIT: In this example, I am deleting things from an external resource, then when that is done, going out the external resource and grabbing a fresh list of the items.
UPDATED CODE
var doSomething = function(parameter){
//send some data to the other function
doAnotherThing(parameter).then(function(){
//now we can go out and retrieve the information
});
};
var doAnotherThing = function(paramers){
return when.promise(function(resolveCallback, rejectCallback) {
//go to a url and grab some data, then resolve it
var s = "some data I got from the url";
resolveCallback({
data: s
});
});
};
The return of doAnotherThing appears to be a promise. You can simply chain a then and put your callback to utilize other. then also already returns a promise. You can return that instead.
// Do stuff
function doSomething(){
return doAnotherThing(parameter).then(function(other){
// Do more stuff
return other
});
}
// Usage
doSomething().then(function(other){
// other
});
Below is how to accomplish what you're trying to do with bluebird.
You can use Promise.resolve() and Promise.reject() within any function to return data in a Promise that can be used directly in your promise chain. Essentially, by returning with these methods wrapping your result data, you can make any function usable within a Promise chain.
var Promise = require('bluebird');
var doSomething = function(parameter) {
// Call our Promise returning function
return doAnotherThing()
.then(function(value) {
// Handle value returned by a successful doAnotherThing call
})
.catch(function(err) {
// if doAnotherThing() had a Promise.reject() in it
// then you would handle whatever is returned by it here
});
}
function doAnotherThing(parameter) {
var s = 'some data I got from the url';
// Return s wrapped in a Promise
return Promise.resolve(s);
}
You can use the async module and its waterfall method to chain the functions together:
var async = require('async');
async.waterfall([
function(parameter, callback) {
doSomething(parameter, function(err, other) {
if (err) throw err;
callback(null, other); // callback with null error and `other` object
});
},
function(other, callback) { // pass `other` into next function in chain
doAnotherThing(other, function(err, result) {
if (err) throw err;
callback(null, result);
})
}
], function(err, result) {
if (err) return next(err);
res.send(result); // send the result when the chain completes
});
Makes it a little easier to wrap your head around the series of promises, in my opinion. See the documentation for explanation.