使用承诺实现回退
Implementing a fallback using promises
这是一种常见的模式,我们在数据源列表中级联,第一次成功打破链条是这样的:
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
等等。但是,如果 getDataN() 函数是异步的,它会导致我们 'callback hell':
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
的 return 值,这本身就是一个承诺,我理解它被链接为输入后续链步。
显然我错了,但是正确的写法是什么?
在大多数承诺库中,您可以像@mido22 的回答那样链接 .fail()
或 .catch()
,但是 jQuery 的 .fail()
不会 "handle" 这样的错误。保证始终传递输入承诺(状态不变),这将不允许所需的 "break" 级联 if/when 成功发生。
唯一可以return具有不同状态(或不同value/reason)的承诺的jQuery承诺方法是.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');
});
作为初学者,遇到了同样的问题,我才意识到 async
和 await
变得多么简单:
同步模式
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
。
这是一种常见的模式,我们在数据源列表中级联,第一次成功打破链条是这样的:
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
等等。但是,如果 getDataN() 函数是异步的,它会导致我们 'callback hell':
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
的 return 值,这本身就是一个承诺,我理解它被链接为输入后续链步。
显然我错了,但是正确的写法是什么?
在大多数承诺库中,您可以像@mido22 的回答那样链接 .fail()
或 .catch()
,但是 jQuery 的 .fail()
不会 "handle" 这样的错误。保证始终传递输入承诺(状态不变),这将不允许所需的 "break" 级联 if/when 成功发生。
唯一可以return具有不同状态(或不同value/reason)的承诺的jQuery承诺方法是.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');
});
作为初学者,遇到了同样的问题,我才意识到 async
和 await
变得多么简单:
同步模式
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
。