0

I have been coding in NodeJS from past 6 months but still, i don't have a clear picture about asynchronous and promise concept. Now coming to the question i will fetch a record using Mongoose from MongoDB which may have branchIds am performing a simple for loop in each iteration am performing a MongoDB operation which is asynchronous(as MongoDB/Mongoose operations are promises). As you know the for loop are synchronous but my function returns the value before the for loop ends. How does it happen? Am attaching the code if my question is not clear leave it as a comment.

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 Function

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 });
    });
};
Kannan T
  • 1,097
  • 2
  • 13
  • 27

1 Answers1

1

You are working with asynchronous (Promises in your case) code in a synchronous way.

This is an async call:

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

Basically you fire this async calls one by one and immediately step over to the return statement, without ever waiting for completion of each fired async call.

The one approach I would definitely advise in your case to look at is async/await, which is basically a synchronous way of writing asynchronous code.

It could go something like this:

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

Of course it can be optimised with parallel promise triggering etc etc. But for basic explanation should be enough.

The crucial change is here:

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

The await keyword within the context of an async function will await for the resolution of a Promise value and will return it without the Promise wrapper, just the raw value or will receive the thrown error, thus allowing to catch it also in a synchronous way with basic try catch.

You can read more on async await here.

Hope this clarifies a bit.

Karen Grigoryan
  • 4,300
  • 2
  • 15
  • 29
  • Hey, thanks allot tried this https://stackoverflow.com/questions/31426740/how-to-return-many-promises-in-a-loop-and-wait-for-them-all-to-do-other-stuff and worked. Moreover the guy from the comment section helped me as well – Kannan T May 06 '18 at 17:48
  • I was able to achieve with single promise.all look at this Promise.all(resAdminDetails.branchIds.map(x => { return pullChefId(x, chef.pkid) })).then(restaurant => { log.debug({ Chef: chef }, 'Chef Details'); return chef; }); – Kannan T May 06 '18 at 17:48
  • @Kannan That's fine if you like it more, just beware `Promise.all` returns an array of resolved values, so in your case it will be an array of `restaurants`. – Karen Grigoryan May 07 '18 at 07:40