Понимать асинхронность и обещание в NodeJS - PullRequest
0 голосов
/ 04 мая 2018

Я кодирую в NodeJS с прошлых 6 месяцев, но, тем не менее, у меня нет четкого представления об асинхронной и многообещающей концепции. Теперь, перейдя к вопросу, я получу запись с использованием Mongoose из MongoDB, которая может иметь branchIds, выполняющий простой цикл for в каждой итерации, выполняющий операцию MongoDB, которая является асинхронной (поскольку операции MongoDB / Mongoose являются обещаниями). Как вы знаете, цикл for является синхронным, но моя функция возвращает значение до окончания цикла for. Как это случилось? Я прилагаю код, если мой вопрос не ясен, оставьте его в качестве комментария.

const restManageChef = (params, query, body) => {
    if (query && parseBoolean(query.superChef)) {
        body = Object.assign(body, { role: 'SUPER-CHEF' });
    } else {
        body = Object.assign(body, { role: 'RES-CHEF' });
    }

    return restPUT(params, query, body).then(chef => {
        return userModel
        .findOne({ restaurantCode: chef.restaurantCode, type: 'RES-ADMIN' })
        .then(resAdminDetails => {
            log.debug({ Chef: chef }, 'Chef Details');
            if (chef.role === 'SUPER-CHEF') {
            log.debug({ BranchIds: resAdminDetails.branchIds }, 'BranchIds');
            for (let i = 0; i < resAdminDetails.branchIds.length; i) {
                log.debug({ BranchIds: resAdminDetails.branchIds[i] }, 'BranchIds');
                pushChefId(resAdminDetails.branchIds[i], chef.pkid)
                .then(restaurant => {
                    log.debug({ Restaurant: restaurant }, 'Restaurant Details');
                })
                .catch(err => {
                    log.error({ err });
                    throw err;
                });
            }
            return chef;
            } else if (chef.role === 'RES-CHEF') {
            for (let i = 0; i < resAdminDetails.branchIds.length; i++) {
                log.debug({ BranchIds: resAdminDetails.branchIds[i] }, 'BranchIds');
                pushChefId(resAdminDetails.branchIds[i], chef.pkid)
                .then(restaurant => {
                    log.debug({ Restaurant: restaurant }, 'Restaurant Details');
                })
                .catch(err => {
                    log.error({ err });
                    throw err;
                });
            }
            return chef;
            }
        });
    });
};

Функция PushChefId

const pushChefId = (restaurantCode, chefId) => {
  return userModel
    .findOneAndUpdate({ restaurantCode }, { $addToSet: { chefIds: chefId } })
    .exec()
    .then(resAdmin => {
      if (!resAdmin) return Promise.reject(`No RES-ADMIN found with restaurantCode - ${restaurantCode}`);

      return storeModel.findByIdAndUpdate(restaurantCode, { $addToSet: { chefIds: chefId } }, { new: true });
    });
};

1 Ответ

0 голосов
/ 05 мая 2018

Вы работаете с асинхронным (в вашем случае Promises) кодом синхронно.

Это асинхронный вызов:

  pushChefId(resAdminDetails.branchIds[i], chef.pkid)
    .then(restaurant => {
      log.debug({
        Restaurant: restaurant
      }, 'Restaurant Details');
    })
    .catch(err => {
      log.error({
        err
      });
      throw err;
    });

По сути, вы запускаете эти асинхронные вызовы один за другим и сразу переходите к оператору возврата, даже не ожидая завершения каждого запущенного асинхронного вызова.

Единственный подход, который я определенно посоветовал бы рассмотреть в вашем случае, это async/await, который по сути является синхронным способом написания асинхронного кода.

Это может пойти примерно так:

const decorateWithRole = (query, body) => {
  return {
    ...body,
    role: (query && parseBoolean(query.superChef) && "RES-CHEF") || "SUPER-CHEF"
  };
};

const restManageChef = async(params, query, body) => {
  const decoratedBody = decorateWithRole(query, body, parseBoolean);

  const chef = await restPUT(params, query, body);
  const resAdminDetails = await userModel.findOne({
    restaurantCode: chef.restaurantCode,
    type: "RES-ADMIN"
  });

  log.debug({
    Chef: chef
  }, "Chef Details");

  if (["SUPER-CHEF", "RES-CHEF"].includes(chef.role)) {
    log.debug({
      BranchIds: resAdminDetails.branchIds
    }, "BranchIds");
    for (let i = 0; i < resAdminDetails.branchIds.length; i++) {
      log.debug({
        BranchIds: resAdminDetails.branchIds[i]
      }, "BranchIds");
      try {
        const restaurant = await pushChefId(
          resAdminDetails.branchIds[i],
          chef.pkid
        );
        log.debug({
          Restaurant: restaurant
        }, "Restaurant Details");
      } catch (err) {
        log.error({
          err
        });
        throw err;
      }
    }
    return chef;
  }
};

const pushChefId = async(restaurantCode, chefId) => {
  const resAdmin = await userModel
    .findOneAndUpdate({
      restaurantCode
    }, {
      $addToSet: {
        chefIds: chefId
      }
    })
    .exec();

  if (!resAdmin) {
    return Promise.reject(
      `No RES-ADMIN found with restaurantCode - ${restaurantCode}`
    );
  }

  return storeModel.findByIdAndUpdate(
    restaurantCode, {
      $addToSet: {
        chefIds: chefId
      }
    }, {
      new: true
    }
  );
};

Конечно, его можно оптимизировать с помощью параллельного запуска обещаний и т. Д. И т. Д. Но для базового объяснения должно быть достаточно.

Важное изменение здесь:

for (let i = 0; i < resAdminDetails.branchIds.length; i++) {
      log.debug({
        BranchIds: resAdminDetails.branchIds[i]
      }, "BranchIds");
      try {
        const restaurant = await pushChefId(
          resAdminDetails.branchIds[i],
          chef.pkid
        );
        log.debug({
          Restaurant: restaurant
        }, "Restaurant Details");
      } catch (err) {
        log.error({
          err
        });
        throw err;
      }
    }
    return chef;
  }
};

Ключевое слово await в контексте функции async будет ожидать разрешения значения Promise и возвратит его без оболочки Promise, только необработанное значение, или получит выданную ошибку, таким образом позволяя поймать его также синхронно с базовым try catch.

Подробнее об асинхронном ожидании вы можете прочитать здесь .

Надеюсь, это прояснит немного.

...