在Javascript(ECMA 6)中替换多个依赖的嵌套异步调用

时间:2017-08-30 10:04:10

标签: javascript asynchronous callback ecmascript-6 async-await

我正在通过调用多个异步加载资源的方法来初始化我的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,依此类推。

我不喜欢这种嵌套解决方案,因为它很难概述和维护。

我的问题是:是否有其他人(专注于“快乐路径”,更好,更短,更少混淆,更容易理解和维护)获得相同结果的可能性?

3 个答案:

答案 0 :(得分:2)

为了避免回调地狱,你需要使用Promises

如果loadA, ..., loadN函数返回promises,那么您只需在每个函数之后调用.then()

loadA().then(function() {
  loadB().then(function() {
    loadC().then(...
  });
});

现在重要的是要记住.then()返回一个Promise,它以其参数的值来解析。

因此,如果loadAloadB都返回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; }