如何使用jQuery Deferred功能而不是async.waterfall?

时间:2014-09-08 12:13:41

标签: javascript asynchronous promise jquery-deferred

我有一系列函数调用并使用async.waterfall。它就像一个魅力。但我想用jQuery Deferred做这件事。如何转换我的代码?

jQuery网站的例子是这样的。两个结果都传递给done函数:

$.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!" );
  }
});

但我的代码不同。我需要将回调传递给waterfall的每一步,并且我在回调中有if秒。如何用jQuery实现它?有可能吗?

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:

所以,我已经实施了一个解决方案,但它没有按预期工作。在.then()中有一个异步API函数调用,jQuery延迟流程在那里被打破。我不知道如何将.then()函数作为API回调。

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 个答案:

答案 0 :(得分:5)

让我们从使用承诺的一般规则开始:

  

每个执行异步操作的函数都必须返回一个承诺

您的案例中有哪些功能?基本上,完整的瀑布,每个瀑布函数都采用了cbVK.Api.call

嗯,VK.Api.call没有返回承诺,而且它是一个库函数,因此我们无法对其进行修改。规则2发挥作用:

  

Create an immediate wrapper代表的所有功能

在我们的例子中,它看起来像这样:

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

现在我们只有承诺,不再需要任何延期。第三条规则发挥作用:

  

使用异步结果执行操作的所有内容都会转入.then回调

     

...并返回其结果。

这个结果也许是一个承诺而不是一个普通的价值,.then可以处理这些 - 并且会给我们一个新的承诺,以便最终执行"某些事情"。所以,让我们链接一些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);
});

至少,那是你原来的瀑布所做的。通过并行执行getCitiesByIdgetCountriesById并删除显式创建这些aRes数组的所有样板,让我们稍微改善一下。

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