我正在通过调用多个异步加载资源的方法来初始化我的JS应用程序,每个方法都依赖于以前的方法。我的直观解决方案是将调用嵌套在回调函数(cba,cbb,cbc,cbd)中,在这些(LoadA,LoadB,LoadC,LoadD)成功完成后,分别从LoadA,LoadB,LoadC,LoadD内部调用:
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,依此类推。
我不喜欢这种嵌套解决方案,因为它很难概述和维护。
我的问题是:是否有其他人(专注于“快乐路径”,更好,更短,更少混淆,更容易理解和维护)获得相同结果的可能性?
答案 0 :(得分:2)
为了避免回调地狱,你需要使用Promises。
如果loadA, ..., loadN
函数返回promises,那么您只需在每个函数之后调用.then()
。
loadA().then(function() {
loadB().then(function() {
loadC().then(...
});
});
现在重要的是要记住.then()
返回一个Promise,它以其参数的值来解析。
因此,如果loadA
和loadB
都返回Promises,您可以将它们链接起来:
loadA().then(function() {
return loadB();
).then(...)
相当于:
loadA().then(loadB).then(loadC).then(...)
更简单!
如果你的函数没有返回一个Promise,那么你需要将它们包含在一个带有辅助函数的函数中。
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
,让您以异步方式使用Promise。
async function() {
await loadA();
await loadB();
...
let finalResult = await loadN();
...
}
答案 1 :(得分:1)
我会使用" Promise"来解决这个问题。对于每个异步调用。
MDN在这里有一个很好的解释:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
您的应用代码可能最终看起来像这样......
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);
现在,如果您将来决定更改您希望调用函数的顺序,那么您根本不需要触及函数中的代码,因为您使用了promises。您只需更改包含承诺链的行:
app.loadA().then(app.loadD).then(app.loadB).then(app.loadC, logError);
(以上示例假设您可以更改应用上的方法以作为承诺。如果应用是第三方的事情,您无法编辑解决方案将是不同的)
答案 2 :(得分:1)
以下是“回调地狱”和使用async/await
可以实现的更好代码的比较:
// dummy implementations:
// these functions call cb with value 1 to 4, after 100ms
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);
function callbackHell() {
loadA( function cba(a) {
loadB( function cbb(b) {
loadC( function cbc(c) {
loadD( function cbd(d) {
console.log([a, b, c, d]);
});
});
});
});
}
async function nicerCode() {
const res = [
await new Promise(loadA),
await new Promise(loadB),
await new Promise(loadC),
await new Promise(loadD)
];
console.log(res);
}
callbackHell();
nicerCode();
.as-console-wrapper { max-height: 100% !important; top: 0; }