Javascript 使用承诺实现回退

Javascript 使用承诺实现回退,javascript,jquery,promise,jquery-deferred,Javascript,Jquery,Promise,Jquery Deferred,这是一种常见的模式,我们在数据源列表中级联,第一次成功地打破了这样的链条: var data = getData1(); if (!data) data = getData2(); if (!data) data = getData3(); $.when(getData1()) .then(function (x) { data = x; }) .fail(function () { return getData2(); }) .then(function (x) { dat

这是一种常见的模式,我们在数据源列表中级联,第一次成功地打破了这样的链条:

var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
$.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; });
等等。但是,如果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
    });
 }
…带着承诺,我希望能写下这样的东西:

var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
$.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
的返回值,它本身就是一个承诺,我理解它是作为后续链步骤的输入链接在一起的


显然我错了,但是写这篇文章的正确方法是什么呢?

在大多数promise库中,您可以像@mido22的答案那样链接
.fail()
.catch()
,但是jQuery的
.fail()
并不能“处理”这样的错误。它总是保证传递输入承诺(状态不变),如果成功发生,这将不允许所需的级联“中断”

唯一可以返回具有不同状态(或不同值/原因)的承诺的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');
});
但在实践中,您可能更典型地创建一个函数或数据对象数组,这些函数或数据对象在数组方法
.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');
});

作为一个初学者,我遇到了同样的问题,我刚刚意识到使用
async
await
,这个问题变得简单多了:

同步模式

现在可以轻松地应用于异步代码:

let data = await getData1();
if (!data) data = await getData2();
if (!data) data = await getData3();

请记住向使用此代码的函数中添加一个
async

getData1
应返回
$。ajax
承诺。首先,您可以继续传递承诺(作为第二个参数),一旦成功,您就完成了承诺,失败时运行回调。我认为在异步函数上不能这样做。为什么不使用递归回调和简单if-else?什么是
$。with()
?你的意思是
$。when()
$。当你只有一个承诺时,when()
是不需要的。如果您修复了
getData1()
以返回一个承诺,那么您只需执行
getData1()
。然后(…)`。这太棒了@Roamer-1888。这很有趣,我还没有看到任何这种解决方案的例子,但这是非常干净的。我会接受它作为一个解决方案,当我验证它的工作+1我最近多次回答了类似的问题,但问题的措辞都非常不同,这可能就是为什么很难找到这些问题的原因。你几乎需要知道答案才能知道要搜索什么,这就是为什么我不断提供新答案,而不是以前的链接。顺便说一句,@Bergi首先让我正确地使用了这种方法。很抱歉,现在没有时间去挖掘链接。@Roamer-1888:Heh,很高兴听到:-)但是我已经在一千个地方使用了
reduce
方法,现在还没有一个我们可以链接的规范。@Bergi,完全同意-我们需要一个规范,它需要至少涵盖两个变体-第一次成功时中断和第一次失败时中断。我想应该涵盖其他变体,先解决后继续。还有其他吗?请记住,目前并非所有浏览器都支持async/await。下面是一个有用的表格,用于检查此功能和其他功能: