Как я обертываю эту подпрограмму внутри Promise, так что я разрешаю только когда получаю все данные?
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
});
})
});
EDIT: Любые проблемы, если я сделаю это так?
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;
}
Поместите эту процедуру в отдельную функцию, поэтому ее легче использовать позже. Эта функция должна вернуть обещание, которое будет разрешено с помощью массива учетных записей (также я изменю ваш код как можно меньше):
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);
}
});
});
});
});
}
Единственное различие - это просто возвращение обещания и разрешение после того, как все учетные записи были получены. Тем не менее, обратные вызовы имеют тенденцию к тому, что ваша кодовая база имеет этот стиль "callback hell", когда у вас много вложенных обратных вызовов, и это затрудняет рассуждение. Вы можете обойти это, используя хорошую дисциплину, но вы можете упростить его, значительно переключаясь на возвращаемые обещания со всех асинхронных функций. Например, ваша функция будет выглядеть следующим образом:
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));
});
}
Оба они абсолютно эквивалентны, и есть множество библиотек, чтобы "обещать" ваши текущие функции обратного вызова (например, bluebird или даже собственный Node util.promisify). Кроме того, с новым синтаксисом async/await он становится еще проще, поскольку он позволяет думать в синхронизации:
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));
}
Как вы можете видеть, мы устраняем любое гнездование! Это упрощает рассуждение о коде, потому что вы можете читать код, поскольку он будет фактически выполнен. Тем не менее, все эти три варианта эквивалентны, так что это зависит от вас, что имеет наибольший смысл в вашем проекте и окружающей среде.
util.promisify
чтобы обернуть OP getAccounts()
и getAccountTx()
? Глядя на документы, на которые вы util.promisify
, довольно ясно, как использовать util.promisify
для обратных вызовов с ошибками; но для примера с документами обертывание функции с обратным вызовом без ошибок с использованием util.promisify («настраиваемые обещанные функции») выглядит так, как будто оно добавляет больше кода, сложности и нечитаемости, чем просто завершение вызова в new 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()
для разрешения последнего шага на массив значений в Promise.all()
формате:
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
}
});
});
}