2017-12-03 96 views
1

如何將此例程封裝在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

回答

2

讓我們把這個例程放到一個單獨的函數中,以便稍後重用它。該函數返回一個承諾,這將賬戶的陣列來解決(我也將修改你的代碼儘可能小):

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

1

我會將每一步分成它自己的函數,然後從每一個函數返回一個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