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

问题描述:

如何将此例程封装在Promise中,以便我只在解析所有数据时才解析?如何同步运行嵌套的异步方法?

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; 
} 
+0

你的异步动作在哪里? –

+0

看到所有承诺:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all用promisAll包装你的每一个,并确保你在每次迭代中返回一个承诺你的“forEach” –

+0

你正在用一组对象填充一个名为'accounts'的数组,每一个对象都有一个账户ID和一个事务,取决于消费这些数据的是什么,但是它对'accounts'是一个'account'对象的数组,每个对象都包含一个'id'值和一个'tx'数组,我期望它被命名为'accountTransactions'而不是'accounts'。 – stone

让我们把这个例程放到一个单独的函数中,以便稍后重用它。该函数返回一个承诺,这将账户的阵列来解决(我也将修改你的代码尽可能小):

function getAccountsWithTx(userId) { 
    return new Promise((resolve, reject) => { 
    var accounts = []; 
    getAccounts(userId, accs => { 
     accs.forEach(acc => { 
     getAccountTx(acc.id, tx => { 
      accounts.push({ 
      'id': acc.id, 
      'tx': tx 
      }); 
      // resolve after we fetched all accounts 
      if (accs.length === accounts.length) { 
      resolve(accounts); 
      } 
     }); 
     }); 
    }); 
    }); 
} 

唯一的差别只是返回一个承诺,解决所有帐户是后牵强。但是,当你有很多嵌套的回调时,回调会让你的代码库具有这种“回调地狱”风格,并且很难推断它。你可以使用良好的纪律来解决它,但是你可以简化它,大大改变从所有异步函数返回promise。例如您的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)); 
    }); 
} 

他们两人都是绝对的等同,而且有plently库来“promisify”您当前的回调风格的功能(例如,bluebird甚至本地节点util.promisify )。此外,随着新async/await syntax它变得更容易,因为它允许考虑同步流:

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)); 
} 

正如你所看到的,我们排除任何嵌套!它使得代码的推理变得更容易,因为你可以读取代码,因为它将被实际执行。但是,所有这三个选项都是相同的,所以这取决于您,在您的项目和环境中最有意义。

+0

你将如何使用util.promisify来封装OP的getAccounts()和getAccountTx()?看看你链接的文档,它很清楚如何使用'util.promisify'进行错误优先回调;但对于文档示例,使用util.promisify(“custom promisified functions”)封装具有非错误优先回调函数的函数看起来像添加了更多的代码,复杂性和不可读性,而不仅仅是将该调用封装在新的Promise() '会。我肯定错过了什么。 – stone

我会将每一步分成它自己的函数,然后从每一个函数返回一个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 
     } 
    }); 
    }); 
} 
+0

我也设法得到这个,但是这是最优雅的方式吗?它看起来像很多工作只是等待所有的数据进行比较勒特。 – Jake

+0

你实际上在这里做了一些复杂的工作:一个异步调用,然后是一个异步调用数组,然后是数据操作,将其转换为所需的格式。我相信有更好的方法可以做到 - 毕竟我只花了20分钟 - 但它们会在相同的概念上有所不同。 – stone