这是一种常见的模式,我们在数据源列表中级联,第一次成功打破了这样的链:
var data = getData1(); if (!data) data = getData2(); if (!data) data = getData3();
等等.但是,如果getDatan()函数是异步的,它会导致我们’回调地狱’:
var data; getData1(function() { getData2(function () { getData3(function () { alert('not found'); }) }) });
实现可能看起来像这样:
function getData1(callback) { $.ajax({ url: '/my/url/1/',success: function(ret) { data = ret },error: callback }); }
…有了承诺,我希望写下这样的东西:
$.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; });
其中第二个.then实际上是指第一个.fail的返回值,它本身就是一个承诺,我理解它被链接作为后续链步骤的输入.
显然我错了,但写这个的正确方法是什么?
解决方法
在最有希望的库,你可以链.fail()或.catch()作为@ mido22的答案,但jQuery的.fail()不“处理”错误本身.保证始终传递输入承诺(具有未更改状态),如果/成功发生,则不允许级联所需的“中断”.
可以返回具有不同状态(或不同值/原因)的promise的唯一jQuery Promise方法是.then().
因此,您可以通过在每个阶段将下一步指定为then的错误处理程序来编写一个继续出错的链.
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'); });
但实际上,您可能更常创建一个函数数组或数据对象,这些函数或数据对象在Array方法.reduce()的帮助下依次调用.
例如 :
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'); });
或者,这可能是一个更好的解决方案:
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'); });