Как заставить запускать вложенные асинхронные методы синхронно?

1

Как я обертываю эту подпрограмму внутри 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;
}
  • 0
    Где ваше асинхронное действие здесь?
  • 0
    см. обещание всем: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… оберните каждый для каждого с помощью promisAll и убедитесь, что вы возвращаете обещание в каждой итерации своего «forEach»
Показать ещё 2 комментария
Теги:
promise

2 ответа

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

Единственное различие - это просто возвращение обещания и разрешение после того, как все учетные записи были получены. Тем не менее, обратные вызовы имеют тенденцию к тому, что ваша кодовая база имеет этот стиль "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));
}

Как вы можете видеть, мы устраняем любое гнездование! Это упрощает рассуждение о коде, потому что вы можете читать код, поскольку он будет фактически выполнен. Тем не менее, все эти три варианта эквивалентны, так что это зависит от вас, что имеет наибольший смысл в вашем проекте и окружающей среде.

  • 0
    Как бы вы использовали util.promisify чтобы обернуть OP getAccounts() и getAccountTx() ? Глядя на документы, на которые вы util.promisify , довольно ясно, как использовать util.promisify для обратных вызовов с ошибками; но для примера с документами обертывание функции с обратным вызовом без ошибок с использованием util.promisify («настраиваемые обещанные функции») выглядит так, как будто оно добавляет больше кода, сложности и нечитаемости, чем просто завершение вызова в new Promise() , Я должен что-то упустить.
1

Я бы разделил каждый шаг на свою собственную функцию и возвратил обещание или обещание от каждого из них. Например, 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
        }
    });
  });
}
  • 0
    Мне тоже удалось это получить, но разве это самый элегантный способ сделать это? Кажется, много работы, чтобы дождаться завершения всех данных.
  • 0
    Вы на самом деле делаете некоторые сложные вещи: асинхронный вызов, за которым следует массив асинхронных вызовов, за которыми следуют манипуляции с данными для получения нужного формата. Я уверен, что есть более изящные способы сделать это - в конце концов, я потратил на это всего 20 минут - но они будут вариациями тех же концепций.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню