본문 바로가기

개발새발 개발자/ReactJS

[ReactJS] 6-3. Async, Await in React

이번 시간에는 Await과 Async를 배운다. 지난 시간에 작성한 fetch-then-catch 라인들을 좀 더 분명하게 작성하도록 도와준다.


1
2
3
4
5
6
7
8
9
10
11
12
componentDidMount() {
    fetch("https://yts.am/api/v2/list_movies.json?sort_by=download_count?sort_by=rating")
      .then(potato => potato.json())
      .then(json => {
        this.setState({
            movies: json.data.movies
        })
        .then(() => .then())
        // CALL BACK HELL!!!!
    })
      .catch(err => console.log(err))
  }
cs

state에 넣기 위해 기존 방식대로 하면 then은 계속 늘어나고 결국 CALL BACK HELL에 빠지게 된다! 대신 새로운 function을 만들어서 해결할 것이다.


1. 함수 만들기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, { Component } from "react"
import "./App.css"
import Movie from "./Movie" //Movie.js에서 여기로 export한 컴포넌트를 import
 
class App extends Component {
  state = {} // 삭제하면 'movies' property를 읽을 수 없음
 
  componentDidMount() {
      this._getMovies();
  }
 
  // 함수를 만들어 놓고
  _renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      // 괄호 넣는 것 잊지 말기
      return <Movie title={movie.title} poster={movie.poster} key={index} /> //key prop으로 index를 작성
    })
    return movies
  }
 
  _getMovies = () => {}
 
  _callApi = () => {
    fetch("https://yts.am/api/v2/list_movies.json?sort_by=download_count?sort_by=rating")
      // 위의 라인이 완료되면 뭔가를 해라
      .then(potato => potato.json())
      .then(json => console.log(json))
      // 근데 그 라인이 에러가 있으면 catch해서 나한테 보여줘라
      .catch(err => console.log(err))
  }
 
  // render에 조건문으로 집어넣기
  render() {
    return (
      <div className="App">
        {// 데이터가 없다면 'Loading'을 띄우고, 있으면 영화정보가 보이도록 한다.
        this.state.movies ? this._renderMovies() : "Loading"}
      </div>
    )
  }
}
 
export default App
 
cs

_makeMovies()0, _callApi()라는 함수를 각각 만들고 componentDidMount()에 있는 내용을 _callApi 안으로 옮긴다.


componentDidMount에는 this._makeMovies()를 넣는다. 왜? didMount를 크게 만들고 싶지 않아서. 많은 function을 불러올 건데 한 군데에 몰아넣는 것보다는 작은 function들이 각기 다른 장소에 있는 게 좋은 코딩이기 때문이다.


2. async하기


async는 asynchronous. 이전 라인의 작업이 끝날 때까지 기다리는 것이 아닐 때.


1
2
3
4
5
6
 _getMovies = async () => {
      const movies = await this._callApi() 
      this.setState({
          movies
      })
  }
cs

_getMovies 앞에 async를 쓰고, moives라는 const 타입의 variable을 만든다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
componentDidMount() {
      this._getMovies();
  }
 
  // 함수를 만들어 놓고
  _renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      // 괄호 넣는 것 잊지 말기
      return <Movie title={movie.title} poster={movie.poster} key={index} /> //key prop으로 index를 작성
    })
    return movies
  }
 
   _getMovies = async () => {
      const movies = await this._callApi() 
      this.setState({
          movies
      })
  }
 
  _callApi = () => {
    fetch("https://yts.am/api/v2/list_movies.json?sort_by=download_count?sort_by=rating")
      // 위의 라인이 완료되면 뭔가를 해라
      .then(potato => potato.json())
      .then(json => console.log(json))
      // 근데 그 라인이 에러가 있으면 catch해서 나한테 보여줘라
      .catch(err => console.log(err))
  }
cs

didMount를 하면, getMovies를 할거고, 이건 asynchronous function인데 movies라는 variable을 가지고 있다. 이 variable은 value를 가지고 있다. callApi라는 function을 await 모드에서 할 수 있는! callApi는 그 아래에 있는 함수이고.


그렇다면 await은 뭐냐? callApi가 끝나는 것을 기다리고(끝나기를 기다리는 것이다. 성공적으로 수행하는 것이 아님) callApi의 return value가 무엇이든 그 value를 movies에 넣는 것이다. 즉, callApi의 return value를 movies에 set할 것이다.


1
2
3
4
5
6
 _getMovies = async () => {
      const movies = await this._callApi() 
      this.setState({
          movies
      })
  }
cs

그리고 이 컴포넌트의 setState를 movies로 할 것이다. 이건 callApi의 return value다. 이 setState는 callApi 작업이 완료되기 전까지는 실행되지 않는다. 다시 한 번 말하지만, 작업이 '완료'되기 전까지다. '성공적 수행'이 아니라. 성공일 수도, 실패일 수도 있다. 어쨌든 작업이 완료되어야 한다. 그 후에, callApi 작업 후에! this.setState가 실행된다.


3. callApi의 return value 설정


1
2
3
4
5
6
7
8
_callApi = () => {
    return fetch("https://yts.am/api/v2/list_movies.json?sort_by=download_count?sort_by=rating")
      // 위의 라인이 완료되면 뭔가를 해라
      .then(potato => potato.json())
      .then(json => return json.data.movies)
      // 근데 그 라인이 에러가 있으면 catch해서 나한테 보여줘라
      .catch(err => console.log(err))
  }
cs

fetch라는 이름의 promise를 return하도록 설정한다. then에서는 json을 return하는 대신에, console.log 하는 대신에, data.movies를 return한다. 


1
2
3
4
5
6
7
8
_callApi = () => {
    return fetch("https://yts.am/api/v2/list_movies.json?sort_by=download_count?sort_by=rating")
      // 위의 라인이 완료되면 뭔가를 해라
      .then(potato => potato.json())
      .then(json => json.data.movies)
      // 근데 그 라인이 에러가 있으면 catch해서 나한테 보여줘라
      .catch(err => console.log(err))
  }
cs

=>에서는 return을 표시하지 않아도 된다. 자동이기 때문. =>는 arrow function이라고 한다. 모던 자바스크립트의 종류. 이것 자체에 return이라는 뜻이 내재되어 있다. 


실행하면, 영화 제목은 나오는데 포스터가 출력되지 않는다. 콘솔을 보면, '포스터가 필요한 항목이라고 적혀져 있으나, value가 undefined 되어있다'고 써있다.


왜냐! movie 오브젝트가 변경되었기 때문. 이전에는 우리가 오브젝트를 만들었다. 하지만 지금은 전혀 다르다. 콘솔로그로 살펴보자.


1
2
3
4
5
6
7
 _renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      console.log(movie)
      return <Movie title={movie.title} poster={movie.poster} key={index} /> 
    })
    return movies
  }
cs


movie 오브젝트에 title이 존재하고, 작동한다. 하지만 포스터는 없다. 


1
2
3
4
5
6
7
8
_renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      console.log(movie)
      return <Movie title={movie.title} poster={movie.poster} key={index} /> 
    })
    return movies
  }
 
cs

그런데도 코드에서는 movie.poster를 찾고 있다.


object 내용을 쭉 보면 large_cover_image가 존재한다. 그래서 poster 대신에 이걸로 바꿔주면 된다.


1
2
3
4
5
6
_renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      return <Movie title={movie.title} poster={movie.large_cover_image} key={index} /> 
    })
    return movies
  }
cs


포스터가 출력되었다.



정리

fetch를 callApi로 변경 -> getMovies라는 asynchronous function을 만들고 -> 그 안에 movies라는 이름의 const variable 생성 -> callApi 작업이 완료되고 return하기를 await 함 -> callApi는 fetch promise를 return하는데 이 모든 데이터는 json임 -> 그 안에 movies가 있는 data라는 이름의 오브젝트와 함께! -> 그래서 json.data.movies 라는 value는 const movies의 결과값이 됨 -> 내 컴포넌트의 state를 movies로 set -> state 안에 movies가 있으면 render movies라는 function을 불러옴 -> render movies는 movies라는 const를 불러옴 -> 이건 this.state.movies.map으로 매핑한다. 영화 title, large_cover_image 순으로.


주의) async를 안 쓰면 await이 작동하지 않는다.


4. key 변경


1
2
3
4
5
6
7
  _renderMovies = () => {
    const movies = this.state.movies.map((movie, index) => {
      // 괄호 넣는 것 잊지 말기
      return <Movie title={movie.title} poster={movie.large_cover_image} key={movie.id} /> 
    })
    return movies
  }
cs

json 데이터에 id가 있으니까 이 데이터로 index 대신 movie.id로 변경. 왜 id를 사용할까? 컴포넌트의 key는 index를 사용하지 않는게 좋다. 느리기 떄문이다.