了解NodeJS中的异步和承诺

时间:2018-05-04 18:18:39

标签: javascript node.js asynchronous promise es6-promise

我过去6个月一直在使用NodeJS进行编码,但我仍然没有关于异步和承诺概念的清晰图片。现在回答这个问题,我将使用来自MongoDB的Mongoose获取记录,这可能让branchIds在每次迭代中执行一个简单的for循环,执行异步的MongoDB操作(因为MongoDB / Mongoose操作是promises)。如您所知,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 :(得分:1)

您正在以同步方式处理异步(在您的情况下为Promises)代码。

这是异步调用:

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

基本上你逐个触发这个异步调用并立即跳转到return语句,而不必等待每个被激活的异步调用的完成。

我肯定会建议您查看的一种方法是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包装的情况下返回它,只是原始的value或将接收抛出的错误,从而允许以基本try catch的同步方式捕获它。

您可以在async等待here上阅读更多内容。

希望这有点澄清。