Javascript 如何使嵌套异步方法同步运行?

Javascript 如何使嵌套异步方法同步运行?,javascript,node.js,promise,Javascript,Node.js,Promise,我如何将这个例程包装在一个承诺中,以便只有在获得所有数据时才能解析 var accounts = []; getAccounts(userId, accs => { accs.forEach(acc => { getAccountTx(acc.id, tx => { accounts.push({ 'id': acc.id, 'tx': tx

我如何将这个例程包装在一个承诺中,以便只有在获得所有数据时才能解析

var accounts = [];
getAccounts(userId, accs => {
    accs.forEach(acc => {
        getAccountTx(acc.id, tx => {
            accounts.push({
                'id': acc.id,
                'tx': tx
            });
        });
    })
});
编辑:如果我这样做有什么问题吗

function getAccountsAllAtOnce() {

    var accounts = [];
    var required = 0;
    var done = 0;

    getAccounts(userId, accs => {
        required = accs.length;
        accs.forEach(acc => {
            getAccountTx(acc.id, tx => {
                accounts.push({
                    'id': acc.id,
                    'tx': tx
                });

                done = done + 1;
            });
        })
    }); 

    while(done < required) {
        // wait
    }

    return accounts;
}
函数getAccountsAllAtOnce(){ var账户=[]; 所需var=0; var done=0; getAccounts(用户ID,accs=>{ 所需=accs.length; accs.forEach(acc=>{ getAccountTx(附件id,tx=>{ 账户推送({ “id”:acc.id, “tx”:tx }); 完成=完成+1; }); }) }); while(完成<需要){ //等等 } 归还账户; }
我将每一步分解为自己的函数,并从每一步返回一个promise或promise数组。例如,getAccounts变为:

function getAccountsAndReturnPromise(userId) {
    return new Promise((resolve, reject) => {
        getAccounts(userId, accounts => {
             return resolve(accounts);
        });
    });
};
getAccountTx解析为{id,tx}对象数组:

function getAccountTransactionsAndReturnPromise(accountId) {
    return new Promise((resolve, reject) => {
        getAccountTx(account.id, (transactions) => {
             var accountWithTransactions = {
                 id: account.id,
                 transactions
             }; 
             return resolve(accountWithTransactions);
        });
    });
};
然后可以使用
Promise.all()
map()
将最后一步解析为所需格式的值数组:

function getDataForUser(userId) {
  return getAccountsAndReturnPromise(userId)
  .then(accounts=>{
    var accountTransactionPromises = accounts.map(account => 
      getAccountTransactionsAndReturnPromise(account.id)
    );
    return Promise.all(accountTransactionPromises);
  })
  .then(allAccountsWithTransactions => {
    return allAccountsWithTransactions.map(account =>{ 
        return { 
            id: account.id, 
            tx: tx
        }
    });
  });
}

让我们把这个例程放到一个单独的函数中,以便以后更容易重用它。此函数应返回一个承诺,该承诺将通过帐户数组解决(同时我将尽可能小地修改您的代码):

唯一的区别就是返回一个承诺并在获取所有帐户后解决。然而,当您有很多嵌套回调时,回调往往会使您的代码库具有这种“回调地狱”风格,这使得您很难对此进行推理。您可以使用良好的规程来解决这个问题,但是您可以通过切换到所有异步函数来返回承诺来大大简化它。例如,您的func将如下所示:

function getAccountsWithTx(userId) {
  getAccounts(userId)
    .then(accs => {
       const transformTx = acc => getAccountTx(acc.id)
         .then(tx => ({ tx, id: acc.id }));

       return Promise.all(accs.map(transformTx));
    });
}
这两个函数是绝对等效的,并且有很多库可以“promisify”您当前的回调风格函数(例如,甚至本机节点)。此外,有了新功能,它变得更加容易,因为它允许同步思考:

async function getAccountsWithTx(userId) {
  const accs = await getUserAccounts(userId);

  const transformTx = async (acc) => {
    const tx = getAccountTx(acc.id);

    return { tx, id: acc.id };
  };

  return Promise.all(accs.map(transformTx));
}

如您所见,我们消除了任何嵌套!它使关于代码的推理变得更加容易,因为您可以按照实际执行的方式阅读代码。然而,所有这三个选项都是等效的,因此,在您的项目和环境中,什么才是最有意义的取决于您。

这里您的异步操作在哪里?请参阅全部承诺:使用promisAll包装您的for each,并确保在项目的每次迭代中都返回一个承诺“forEach'您正在用一组对象填充一个名为
accounts
的数组,每个对象都有一个帐户ID和一个事务。取决于消耗此数据的内容,但对于
帐户
而言,将其作为
帐户
对象的数组更具可读性,每个对象包含一个
id
值和一个
tx
数组。我希望它被命名为
accountTransactions
,而不是
accounts
。您不能同步运行异步函数(因此它们会立即返回结果)。你所能做的就是按顺序运行它们。我也设法做到了这一点,但这是最优雅的方式吗?仅仅等待所有数据完成似乎需要做很多工作。实际上,您正在做一些复杂的事情:一个异步调用,然后是一个异步调用数组,然后是数据操作,以将其转换为所需的格式。我相信还有更优雅的方法可以做到这一点——毕竟我只花了20分钟——但它们是相同概念的变体。您如何使用
util.promisify
来包装OP的
getAccounts()
getAccountTx()
?查看您链接的文档,很清楚如何使用
util.promisify
进行错误优先回调;但对于docs示例,使用util.promisify(“自定义promisified函数”)将函数包装为非错误优先回调,这似乎比仅将调用包装在
new Promise()
中会增加更多代码、复杂性和不可读性。我一定错过了什么。
async function getAccountsWithTx(userId) {
  const accs = await getUserAccounts(userId);

  const transformTx = async (acc) => {
    const tx = getAccountTx(acc.id);

    return { tx, id: acc.id };
  };

  return Promise.all(accs.map(transformTx));
}