替换Javascript(ECMA 6)中的多个依赖嵌套异步调用
我通过调用大量异步加载Resources的方法来初始化我的JS应用程序,每个方法都依赖于前面的方法。我直观的解决方案是将调用嵌套在回调函数(cba、cbb、cbc、cbd)中,在这些(LoadA、LoadB、LoadC、LoadD)成功完成后,分别从LoadA、LoadB、LoadC、LoadD的内部调用:替换Javascript(ECMA 6)中的多个依赖嵌套异步调用,javascript,asynchronous,callback,ecmascript-6,async-await,Javascript,Asynchronous,Callback,Ecmascript 6,Async Await,我通过调用大量异步加载Resources的方法来初始化我的JS应用程序,每个方法都依赖于前面的方法。我直观的解决方案是将调用嵌套在回调函数(cba、cbb、cbc、cbd)中,在这些(LoadA、LoadB、LoadC、LoadD)成功完成后,分别从LoadA、LoadB、LoadC、LoadD的内部调用: app.LoadA( function cba() { app.LoadB( function cbb() { ....
app.LoadA( function cba() {
app.LoadB( function cbb() {
....
app.LoadC( function cbc() {
...
app.LoadD( function cbd() {
...
} );
} );
} );
} );
LoadA( cb )
{
let url = '.........'; // just an url
let req = new XMLHttpRequest();
req.open('GET', url, true);
req.responseType = 'arraybuffer';
let lh = this;
req.onload = function ol(event)
{
let arrayBuffer = req.response;
let loadedObject = Convert( arrayBuffer, ......... );
cb( loadedObject ); // call, if successed!
}
req.send(null);
}
....
LoadA在不加载对象的情况下返回,因此LoadB必须等待LoadA的嵌入式onload函数调用回调cb,以此类推
我不喜欢这种嵌套解决方案,因为它很难概述和维护
我的问题是:是否有其他(专注于“快乐之路”,更好、更短、更少混乱、更容易理解和维护)的可能性来实现相同的结果?我会为每个异步调用使用一个“承诺”来打破这一局面 MDN在这里对如何使用它们有很好的解释: 你的应用程序代码可能最终看起来像这样
app = {
loadA: function() {
return new Promise((resolve) => {
console.log('Loading A');
resolve();
});
},
loadB: function () {
return new Promise((resolve) => {
console.log('Loading B');
resolve();
});
},
// For demonstration, I have coded loadC with a rejection if it fails
// (Success is based on a random boolean)
loadC: function () {
return new Promise((resolve, reject) => {
console.log('Loading C');
var success = Math.random() >= 0.5;
if( success ){
resolve();
} else {
reject('C did not load');
}
});
},
loadD: function () {
return new Promise((resolve) => {
console.log('Loading D');
resolve();
});
}
};
// A global function for reporting errors
function logError(error) {
// Record the error somehow
console.log(error);
}
现在把他们叫做承诺链
app.loadA().then(app.loadB).then(app.loadC, logError).then(app.loadD);
现在,如果将来您决定更改希望调用函数的顺序,则根本不需要触摸函数中的代码,因为您使用了承诺。您只需更改包含承诺链的行:
app.loadA().then(app.loadD).then(app.loadB).then(app.loadC, logError);
(上面的示例假设您可以将应用程序上的方法更改为承诺。如果应用程序是第三方的东西,您无法编辑,则解决方案会有所不同)我将使用每个异步调用的“承诺”来打破这一点 MDN在这里对如何使用它们有很好的解释: 你的应用程序代码可能最终看起来像这样
app = {
loadA: function() {
return new Promise((resolve) => {
console.log('Loading A');
resolve();
});
},
loadB: function () {
return new Promise((resolve) => {
console.log('Loading B');
resolve();
});
},
// For demonstration, I have coded loadC with a rejection if it fails
// (Success is based on a random boolean)
loadC: function () {
return new Promise((resolve, reject) => {
console.log('Loading C');
var success = Math.random() >= 0.5;
if( success ){
resolve();
} else {
reject('C did not load');
}
});
},
loadD: function () {
return new Promise((resolve) => {
console.log('Loading D');
resolve();
});
}
};
// A global function for reporting errors
function logError(error) {
// Record the error somehow
console.log(error);
}
现在把他们叫做承诺链
app.loadA().then(app.loadB).then(app.loadC, logError).then(app.loadD);
现在,如果将来您决定更改希望调用函数的顺序,则根本不需要触摸函数中的代码,因为您使用了承诺。您只需更改包含承诺链的行:
app.loadA().then(app.loadD).then(app.loadB).then(app.loadC, logError);
(以上示例假设您可以更改应用程序上的方法,使其作为承诺工作。如果应用程序是第三方,您无法编辑,则解决方案将有所不同)下面是“回调地狱”与使用async/await
可以实现的更好代码的比较:
//虚拟实现:
//这些函数在100ms后调用值为1到4的cb
const loadA=(cb)=>setTimeout(=>cb(1),100);
const loadB=(cb)=>setTimeout(=>cb(2),100);
const loadC=(cb)=>setTimeout(=>cb(3),100);
const loadD=(cb)=>setTimeout(=>cb(4),100);
函数callbackHell(){
loadA(功能cba(a){
loadB(功能cbb(b){
loadC(功能cbc(c){
loadD(功能cbd(d){
日志([a,b,c,d]);
});
});
});
});
}
异步函数nicerCode(){
常数res=[
等待新的承诺(loadA),
等待新的承诺(loadB),
等待新的承诺(loadC),
等待新的承诺(loadD)
];
控制台日志(res);
}
callbackHell();
nicerCode()代码>
.as控制台包装{max height:100%!important;top:0;}
这里比较了“回调地狱”和使用async/await可以实现的更好的代码:
//虚拟实现:
//这些函数在100ms后调用值为1到4的cb
const loadA=(cb)=>setTimeout(=>cb(1),100);
const loadB=(cb)=>setTimeout(=>cb(2),100);
const loadC=(cb)=>setTimeout(=>cb(3),100);
const loadD=(cb)=>setTimeout(=>cb(4),100);
函数callbackHell(){
loadA(功能cba(a){
loadB(功能cbb(b){
loadC(功能cbc(c){
loadD(功能cbd(d){
日志([a,b,c,d]);
});
});
});
});
}
异步函数nicerCode(){
常数res=[
等待新的承诺(loadA),
等待新的承诺(loadB),
等待新的承诺(loadC),
等待新的承诺(loadD)
];
控制台日志(res);
}
callbackHell();
nicerCode()代码>
.as控制台包装{max height:100%!important;top:0;}
要避免回调地狱,您需要使用
如果loadA,…,loadN
函数返回承诺,那么您只需依次在每个函数之后调用.then()
loadA().then(function() {
loadB().then(function() {
loadC().then(...
});
});
现在重要的是要记住,.then()
返回一个与参数值解析的承诺
因此,如果loadA
和loadB
都返回承诺,您可以像这样链接它们:
loadA().then(function() {
return loadB();
).then(...)
这相当于:
loadA().then(loadB).then(loadC).then(...)
简单多了
如果你的函数没有返回一个承诺,那么你需要用一个helper函数将它们封装在一个承诺中
function wrapInsidePromise(f) {
return new Promise(function(resolve, reject) {
f(function() {
resolve();
});
});
}
var pLoadA = wrapInsidePromise(app.loadA);
var pLoadB = wrapInsidePromise(app.loadB);
...
pLoadA().then(pLoadB).then(...);
更重要的是,在ES6中,您可以使用async/await
,这使您能够以异步方式使用承诺
要避免回调地狱,您需要使用
如果loadA,…,loadN
函数返回承诺,那么您只需依次在每个函数之后调用.then()
loadA().then(function() {
loadB().then(function() {
loadC().then(...
});
});
现在重要的是要记住,.then()
返回一个与参数值解析的承诺
因此,如果loadA
和loadB
都返回承诺,您可以像这样链接它们:
loadA().then(function() {
return loadB();
).then(...)
这相当于:
loadA().then(loadB).then(loadC).then(...)
简单多了
如果你的函数没有返回一个承诺,那么你需要用一个helper函数将它们封装在一个承诺中
function wrapInsidePromise(f) {
return new Promise(function(resolve, reject) {
f(function() {
resolve();
});
});
}
var pLoadA = wrapInsidePromise(app.loadA);
var pLoadB = wrapInsidePromise(app.loadB);
...
pLoadA().then(pLoadB).then(...);
更重要的是,在ES6中,您可以使用async/await
,这使您能够以异步方式使用承诺
我保证有更好的方法:@Trantor上面的代码将类似于app.LoadA.then(app.LoadB)。then(app.LoadC)。then(app.LoadD)。then(…)
。这真的更让人困惑吗?@Andy那么你最好等待OP的回复。(对不起,这花了我太长时间)@evolutionxbox谢谢你的延迟回复。@Andy没问题。我希望这有助于解决这个问题