问题
I have a chain of function calls and use async.waterfall. It works like a charm. But I'd like to do it with jQuery Deferred. How to transform my code?
The example from jQuery site is like this. Both results are passed to done
function:
$.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ).done(function( a1, a2 ) {
// a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively.
// Each argument is an array with the following structure: [ data, statusText, jqXHR ]
var data = a1[ 0 ] + a2[ 0 ]; // a1[ 0 ] = "Whip", a2[ 0 ] = " It"
if ( /Whip It/.test( data ) ) {
alert( "We got what we came for!" );
}
});
But my code is different. I need to pass a callback to every step of a waterfall
and I have if
s in callbacks. How to implement it with jQuery? Is it possible?
async.waterfall([
function(cb) {
VK.Api.call('users.get', {user_ids: res.session.mid, fields: fields}, function(userDataRes) {
cb(null, userDataRes);
});
},
function(userDataRes, cb) {
if(userDataRes.response[0].city) {
VK.Api.call('database.getCitiesById', {city_ids: userDataRes.response[0].city}, function(cityDataRes) {
cb(null, userDataRes, {city: cityDataRes.response[0].name});
});
}
else {
cb(null, userDataRes, {});
}
},
function(userDataRes, cityDataRes, cb) {
if(userDataRes.response[0].country) {
VK.Api.call("database.getCountriesById", {country_ids: userDataRes.response[0].country}, function(countryDataRes) {
cb(null, userDataRes, cityDataRes, {country: countryDataRes.response[0].name});
});
}
else {
cb(null, userDataRes, {}, {});
}
},
function(userDataRes, cityDataRes, countryDataRes, cb) {
var resObj = $.extend(true, {}, userDataRes.response[0], cityDataRes, countryDataRes);
cb(null, resObj);
},
],
function(err, res) {
console.log("res::: ", res);
}
);
UPD 1:
So, I've implemented a solution, but it doesn't work as expected. There is an asynchronous API function call in .then()
and jQuery deferred flow is broken there. I don't know how to make a .then()
function as an API callback.
var dfr = $.Deferred();
dfr.then(function(val) {
// THIS is an asynchronous API function call. And its callback returns result that is passed to the next .then()
// But jQuery deferred flow doesn't follow this API call.
// It goes along to the next .then ignoring this API call.
// How to make it enter this API call and be returned from a API's callback.
VK.Api.call('users.get', {user_ids: res.session.mid, fields: fields}, function(userDataRes) {
// cb(null, userDataRes);
console.log("countryDataRes: ", userDataRes);
return userDataRes;
});
}).
then(function(userDataRes) {
console.log("countryDataRes: ", userDataRes);
if(userDataRes.response[0].city) {
VK.Api.call('database.getCitiesById', {city_ids: userDataRes.response[0].city}, function(cityDataRes) {
// cb(null, userDataRes, {city: cityDataRes.response[0].name});
return [userDataRes, {city: cityDataRes.response[0].name}];
});
}
else {
// cb(null, userDataRes, {});
return [userDataRes, {}];
}
}).
then(function(aRes) {
if(aRes[0].response[0].country) {
VK.Api.call("database.getCountriesById", {country_ids: aRes[0].response[0].country}, function(countryDataRes) {
// cb(null, userDataRes, cityDataRes, {country: countryDataRes.response[0].name});
return [aRes[0], aRes[1], {country: countryDataRes.response[0].name}];
});
}
else {
cb(null, aRes[0], {}, {});
}
}).
then(function(aRes) {
var resObj = $.extend(true, {}, aRes[0].response[0], aRes[1], aRes[2]);
console.log("cityDataRes: ", aRes[1]);
console.log("countryDataRes: ", aRes[2]);
cb(null, resObj);
return resObj;
}).
done(function(res) {
console.log("res::: ", res);
});
dfr.resolve();
回答1:
Let's start with the general rule for using promises:
Every function that does something asynchronous must return a promise
Which functions are these in your case? Basically, the complete waterfall, each of the waterfall functions that took a cb
and VK.Api.call
.
Hm, VK.Api.call
doesn't return a promise, and it's a library function so we cannot modify it. Rule 2 comes into play:
Create an immediate wrapper for every function that doesn't
In our case, it will look like this:
function callApi(method, data) {
var dfr = $.Deferred();
VK.Api.call(method, data, function(result) {
dfr.resolve(result);
});
// No error callbacks? That's scary!
// If it does offer one, call `dfr.reject(err)` from it
return dfr.promise();
}
Now we have only promises around, and do no more need any deferreds. Third rule comes into play:
Everything that does something with an async result goes into a
.then
callback…and returns its result.
That result might as well be a promise not a plain value, .then
can handle these - and will give us back a new promise for the eventual result of executing the "something". So, let's chain some then()
s:
apiCall('users.get', {user_ids: res.session.mid, fields: fields})
.then(function(userDataRes) {
console.log("countryDataRes: ", userDataRes);
if (userDataRes.response[0].city) {
return apiCall('database.getCitiesById', {city_ids: userDataRes.response[0].city})
.then(function(cityDataRes) {
return [userDataRes, {city: cityDataRes.response[0].name}];
});
} else {
return [userDataRes, {}];
}
})
.then(function(aRes) {
if (aRes[0].response[0].country) {
return apiCall("database.getCountriesById", {country_ids: aRes[0].response[0].country})
.then(function(countryDataRes) {
return [aRes[0], aRes[1], {country: countryDataRes.response[0].name}];
});
} else {
return [aRes[0], aRes[1], {}];
}
})
.then(function(aRes) {
var resObj = $.extend(true, {}, aRes[0].response[0], aRes[1], aRes[2]);
console.log("cityDataRes: ", aRes[1]);
console.log("countryDataRes: ", aRes[2]);
return resObj;
})
.done(function(res) {
console.log("res::: ", res);
});
At least, that's what your original waterfall did. Let's polish it up a bit by executing getCitiesById
and getCountriesById
in parallel, and removing all the boilerplate of explicitly creating these aRes
arrays.
function callApi(method, data) {
var dfr = $.Deferred();
VK.Api.call(method, data, function(result) {
dfr.resolve(result.response[0]);
// changed: ^^^^^^^^^^^^
});
// No error callbacks? That's scary!
// If it does offer one, call `dfr.reject(err)` from it
return dfr.promise();
}
apiCall('users.get', {user_ids: res.session.mid, fields: fields})
.then(function(userData) {
if (userData.city)
var cityProm = apiCall('database.getCitiesById', {city_ids: userData.city});
if (userData.country)
var countryProm = apiCall("database.getCountriesById", {country_ids: userData.country});
return $.when(cityProm, countrProm).then(function(city, country) {
var resObj = $.extend(true, {}, userData);
if (city)
resObj.city = city.name;
if (country)
resObj.country = country.name;
return resObj;
});
})
.done(function(res) {
console.log("res::: ", res);
});
来源:https://stackoverflow.com/questions/25724015/how-to-use-jquery-deferred-functionality-instead-of-async-waterfall