【问题标题】:Implementing a fallback using promises使用 Promise 实现回退
【发布时间】:2015-08-22 16:02:32
【问题描述】:

这是一种常见的模式,我们在数据源列表中级联,第一次成功打破数据链,如下所示:

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 的返回值,它本身就是一个承诺,我理解它是作为后续链步骤的输入链接的。

显然我错了,但正确的写法是什么?

【问题讨论】:

  • getData1 应该返回 $.ajax 承诺。
  • 第一个想法:您可以继续传递承诺(作为第二个参数),成功时完成承诺,失败时运行回调。
  • 我认为你不能在异步函数上这样做。为什么不使用递归回调和简单的 if-else?
  • 什么是$.with()?你的意思是$.when()
  • $.when() 当你只有一个承诺时不需要。如果你修复getData1() 来返回一个承诺,那么你可以只做getData1().then(...)`。

标签: javascript jquery promise jquery-deferred


【解决方案1】:

在大多数 Promise 库中,您可以将 .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');
});

但在实践中,您可能更典型地创建一个函数或数据对象数组,这些函数或数据对象在数组方法.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');
});

【讨论】:

  • 这太棒了@Roamer-1888。令人着迷的是,我还没有看到任何这种解决方案的例子,但这很干净。当我验证它有效时,我会接受它作为解决方案。 +1
  • 我最近多次回答过类似的问题,但问题的措辞都非常不同,这可能是它们很难找到的原因。您几乎需要知道答案才能知道要搜索什么,这就是为什么我不断提供新答案而不是以前的链接。
  • 顺便说一句,感谢@Bergi 让我首先正确地采用了这种方法。抱歉,现在没有时间挖掘链接。
  • @Roamer-1888:呵呵,很高兴听到 :-) 但是我已经在上千个地方使用了reduce 方法,还没有我们可以链接的规范。
  • @Bergi,完全同意——我们需要一个规范的,它至少需要涵盖两个变体——先成功后中断和先失败后中断。我想应该涵盖其他变体,首先解决和继续,不管。还有其他人吗?
【解决方案2】:

作为一个初学者,偶然发现了同样的问题,我才意识到使用asyncawait 变得多么简单:

同步模式

var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();

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

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

请记住在使用此代码的函数中添加async

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 2011-09-09
  • 1970-01-01
  • 1970-01-01
  • 2016-12-08
  • 1970-01-01
相关资源
最近更新 更多