[TIL] 게시판 좋아요 기능 구현, 비동기/동기 함수 처리

김시원·2023년 4월 22일
0

TIL

목록 보기
9/50

Issues encountered

  1. 좋아요 버튼 구현을 위한 DB 모델 설계
  2. map() 안에서 비동기 함수 수행시 빈 배열 출력

What I tried

  1. 좋아요 기능 구현

    1. 아이디어 1
      1) Likes table을 만들어서 likeId (key), UserId, PostId를 담는다.
      2) Posts table에 likes attribute를 추가한다.
      3) 유저가 좋아요 POST 요청을 하면, Likes table에서 유저가 이미 좋아요를 눌렀는지 확인하고 (UserId, PostId를 비교한다):
      - 좋아요 등록: row가 없으면 create 해준다.
      1. Posts table의 likes를 1만큼 증가시켜준다 (increase())
      - 좋아요 취소: row가 있을 때 destroy해준다.
      1. Posts table의 likes를 1만큼 감소시켜준다 (decrease())
    2. 아이디어 2
      1) Likes table을 만들어서 likeId (key), UserId, PostId를 담는다.
      2) 유저가 좋아요 POST 요청을 하면, Likes table에서 유저가 이미 좋아요를 눌렀는지 확인하고 (UserId, PostId를 비교한다):
      - 좋아요 등록: row가 없으면 create 해준다.
      - 좋아요 취소: row가 있을 때 destroy해준다.
      3) 게시글 조회 GET 요청이 들어오면 Likes 테이블에서 해당 post에 해당하는 row가 몇 개인지 count한다.

    Q. Posts에 attribute를 추가하는 게 좋을까, 게시물 조회 GET 요청이 들어올 때만 Likes table에서 해당 post의 좋아요 개수를 세는게 좋을까?

    A. ChatGPT: 두 아이디어 중에서는 첫 번째 아이디어가 더 효율적일 것입니다. 이유는 아이디어 2에서는 게시글 조회할 때마다 Likes 테이블에서 count를 하기 때문에 데이터베이스 부하가 많이 걸릴 수 있기 때문입니다. 반면, 아이디어 1에서는 Posts table에 likes attribute를 추가하고, 좋아요 등록/취소시에만 해당 attribute 값을 증가/감소시켜주기 때문에 데이터베이스 부하가 적을 것입니다. 또한, 아이디어 1에서는 Posts table에 likes attribute가 있기 때문에 게시글 조회 시에 좋아요 개수를 빠르게 가져올 수 있습니다.

  2. 동기 함수 안에서 비동기 함수를 수행했을 때 처리

    const posts = await likedPosts.map(async (post) => {
    		const targetPost = await Posts.findByPk(post.PostId);
    		const p = {
    			postId: targetPost.postId,
    			userId: targetPost.UserId,
    			nickname: targetPost.nickname,
    			title: targetPost.title,
    			createdAt: targetPost.createdAt,
    			updatedAt: targetPost.updatedAt,
    			likes: targetPost.likes,
    		};
    		return p;
    	});
    	res.status(200).json({ posts }); 
    

    결과:

    1. 위 코드에서 posts에 빈 배열이 담기는 이유는, likedPosts.map() 함수 내부의 콜백 함수가 비동기 함수이기 때문이다. 이 경우, map() 함수는 각 콜백 함수의 실행이 완료되기 전에 다음 반복을 수행하므로, 모든 비동기 함수가 완료되기 전에 posts 배열이 반환될 수 있다. 이로 인해 posts 배열이 비어 있는 상태로 반환될 수 있다.
    2. 이를 해결하기 위해서는, likedPosts.map() 함수의 콜백 함수를 Promise.all()을 사용하여 Promise 배열로 만든 다음, 모든 Promise가 완료된 후에 posts 배열을 반환해야 한다.
    const postPromises = likedPosts.map(async (post) => {
      const targetPost = await Posts.findByPk(post.PostId);
      const p = {
        postId: targetPost.postId,
        userId: targetPost.UserId,
        nickname: targetPost.nickname,
        title: targetPost.title,
        createdAt: targetPost.createdAt,
        updatedAt: targetPost.updatedAt,
        likes: targetPost.likes,
      };
      return p;
    });
    
    const posts = await Promise.all(postPromises);
    res.status(200).json({ posts });

위 코드에서는 likedPosts.map() 함수를 실행하여 Promise 배열을 만들고, Promise.all() 함수를 사용하여 모든 Promise가 완료될 때까지 기다린 후, posts 배열을 반환한다. 이렇게 함으로써 모든 p 객체를 담은 posts 배열이 반환될 것이다.

Q. Promise.all()은 어떤 메서드일까?

A. Promise.all()은 여러 개의 Promise를 인자로 받아, 모든 Promise가 완료될 때까지 기다린 후에 결과를 반환하는 메서드이다.

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then((result) => {
    console.log(result); // [1, 2, 3]
  });

Q. 그럼 왜 map() 메서드 앞에 await 키워드를 사용하면 안 될까?

A. map() 메서드는 콜백 함수를 실행하여 새로운 배열을 반환하는 동기 함수이기 때문이다. 따라서 map() 메서드가 반환하는 것은 배열이며, 배열에 대해서는 await 키워드를 사용할 수 없다.
배열에 대해서 비동기 작업을 수행해야 하는 경우에는, map() 메서드를 사용하여 새로운 배열을 만든 다음, 그 배열에 대해서 Promise.all() 메서드를 사용하여 비동기 작업을 수행할 수 있다. 이렇게 함으로써, 모든 비동기 작업이 완료될 때까지 기다렸다가 결과를 반환할 수 있다.

How I resolved the issues

  1. map() 안에서 비동기 함수를 처리하고 map()으로 새롭게 반환한 배열을 map() scope 밖에서 사용하였을 때 빈 배열이 반환되었고, 이는 map() 자체는 동기 함수이기 때문이었다. 그래서 Promise.all() 함수를 사용하여 모든 Promise들이 반환될 때까지 기다려 준 후 posts 배열을 반환한다.
  • 굳이 map() 안에 비동기 처리를 할 필요 없이 다음과 같이 따로 처리해도 된다.
const postIds = getLiked.map((like) => {
			return like.dataValues.postId;
		});
const getPosts = await Posts.findAll({
  where: {
    postId: {
      [Op.in]: postIds,
    },
  },
});

What I newly learned

  1. Socket.io
  2. Access token & Refresh token
  3. sequelize increment() & decrement() methods

What to learn next

  1. Transaction
  2. OOP
  3. Layered Architecture

0개의 댓글