简体   繁体   中英

What is the best way to use nested promises and await

I want to fetch all posts of the current user and all posts of his friends. The code is working but I'm confused about using async-await and Promise.all together. How should I handle errors when I use them together and what's the best way to use nested async functions? Thanks in advance.

router.get("/timeline", verify, async(req, res, next) => {
  let postArray = [];
  try {
    const posts = await Post.find({
      userId: req.user.id
    });
    postArray.push(...posts);
    const currentUser = await User.findById(req.user.id);
    const promises = currentUser.friends.map(async(friendId) => {
      const posts = await Post.find({
        userId: friendId
      });
      posts.map((p) => postArray.push(p));
    });
    await Promise.all(promises).then(() => res.send(postArray));
  } catch (err) {
    next(err);
  }
});

verify middleware is by JWT validator. And my models are like these:

const UserSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      min: 3,
      max: 20,
      unique: true,
    },
    email: {
      type: String,
      required: true,
      max: 50,
      unique: true,
    },
    password: {
      type: String,
      required: true,
      min: 6,
      max: 1024,
    },
    profilePicture: {
      type: String,
      default: "",
    },
    coverPicture: {
      type: String,
      default: "",
    },
    isAdmin: {
      type: Boolean,
      default: false,
    },
    friends: {
      type: Array,
      default: [],
    },
  },
  { timestamps: true }
);
const PostSchema = new mongoose.Schema(
  {
    userId: {
      type: String,
      required: true,
    },
    desc: {
      type: String,
      max: 500
    },
    img: {
      type: String,
    },
    likes: {
      type: Array,
      default: [],
    },
  },
  { timestamps: true }
);

Here's my simplified version:

router.get("/timeline", verify, async (req, res, next) => {
    try {
        const postArray = await Post.find({ userId: req.user.id });
        const currentUser = await User.findById(req.user.id);
        await Promise.all(currentUser.friends.map(friendId => {
            return Post.find({ userId: friendId }).then(fPosts => postArray.push(...fPosts));
        }));
        res.send(postArray);
    } catch (err) {
        next(err);
    }
});

Changes:

  1. Just initialize postArray with the first bit of data, rather than declaring it as an empty array and then pushing into it from another array.
  2. Rather than iterate posts each time, just push them all into postArray with one operation
  3. I'm not a fan of using an async callback with await in a .map() because it implies to the casual reader that the .map() loop will actually pause for the await (which it will not). So, instead I just use .then() to collect the friend posts into postArray and return the promise from Post.find() .

I'm confused about using async-await and Promise.all together

You use Promise.all() anytime you have more than one promise and you want to know when they are all done. This can be used with async/await just fine because they also operation on promises. In my example, I do return Post.find() inside the .map() loop which means the return from .map() will be an array of promises which is perfect to use with Promise.all() to know when they are all done.

How should I handle errors when I use them together

If you await Promise.all() , then error handling for that works just like all your other await statements. Errors will be caught by your surrounding try/catch .

what's the best way to use nested async functions

There's really no single answer to this. You had one approach that worked. I offered a slight variation on it that seemed simpler to me. Simplicity is in the eye of the beholder.


FYI, some people don't like seeing .then() and await in the same block of code - expecting you to go with one idiom or the other. I don't ascribe to those type of black and white rules, but prefer to use whatever seems simpler to me which occasionally sees me mix them for a particular situation.

Here's a modified version of the code above without the .then() if you so prefer:

router.get("/timeline", verify, async (req, res, next) => {
    try {
        const postArray = await Post.find({ userId: req.user.id });
        const currentUser = await User.findById(req.user.id);
        await Promise.all(currentUser.friends.map(async friendId => {
            let fPosts = await Post.find({ userId: friendId });
            postArray.push(...fPosts);
        }));
        res.send(postArray);
    } catch (err) {
        next(err);
    }
});

jfriend00 's answer is great. Just adding to it, if you wanted to show a custom error if your api fails to load friend's post, you can do so by doing something like this.

router.get("/timeline", verify, async (req, res, next) => {
    try {
        const postArray = await Post.find({ userId: req.user.id });
        const currentUser = await User.findById(req.user.id);
        await Promise.all(currentUser.friends.map(async friendId => {
            let fPosts = await Post.find({ userId: friendId });
            postArray.push(...fPosts);
        })).catch((e) => {
             throw new Error("Failed to load friend's post")    // notice a separate catch 
        })
        res.send(postArray);
    } catch (err) {
        next(err);
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM