setTimeout을 통해 이전 함수의 처리 결과를 기다린 후, callback을 실행시키는 방식으로 비동기 처리를 구현할 수 있다.
Promise 객체란 완료가 예정된 작업의 실행 결과를 의미하는 대리자이다. ES6에서 채택되었다.
Promise의 콜백 함수(executor)는 기본적으로 resolve(), reject() 메서드를 가지며, 둘 중 하나를 반드시 반환해야 한다.
new Promise(콜백) - 대기(pending) 상태를 갖는다. 결과값은 undefined이다.
resolve(value) - 콜백(executor)이 성공적으로 이행(fulfilled)된 상태이다. 결과값으로 value를 반환한다.
rejected(error) - 콜백(executor)이 특정한 사유로 불이행(error)된 상태이다. Error 객체를 반환한다.
콜백만을 이용해 비동기 처리를 하면 아래와 같다.
function increaseAndPrint(n, callback) {
setTimeout(() => {
const increased = n + 1;
console.log(increased);
if (callback) {
callback(increased);
}
}, 1000);
}
increaseAndPrint(0, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
increaseAndPrint(n, (n) => {
console.log("끝!");
});
});
});
});
});
Promise 객체를 이용해 비동기 처리를 하면 아래와 같다.
function increaseAndPrint(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const value = n + 1;
if (value === 5) {
const error = new Error();
error.name = "ValueIsFiveError";
reject(error);
return;
}
console.log(value);
resolve(value);
}, 1000);
});
}
increaseAndPrint(0)
.then((n) => {
return increaseAndPrint(n);
})
.then((n) => {
return increaseAndPrint(n);
})
.then((n) => {
return increaseAndPrint(n);
})
.then((n) => {
return increaseAndPrint(n);
})
.then((n) => {
return increaseAndPrint(n);
})
.finally(() => {
console.log("끝!");
})
.catch((e) => {
console.error(e);
});
Promise 객체의 resolve(value) 메서드의 반환값을 .then 인스턴스 메서드를 통해 n으로 이어 받으며 코드의 가독성이 좋아졌다.
또한 .catch 인스턴스 메서드를 통해 에러 발생에도 대처할 수 있게 되었다. 추가적으로 .finally 인스턴스 메서드는 결과에 관계없이 프라미스가 처리되면 실행되며, 인수를 받지 않는다.
ES8(2017)에 추가된 문법이다.
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function makeError() {
await sleep(1000);
const error = new Error();
throw error;
}
async function process() {
try {
await makeError();
} catch (e) {
console.error(e);
}
}
process();
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const getDog = async () => {
await sleep(1000);
return "멍멍이";
};
const getRabbit = async () => {
await sleep(500);
return "토끼";
};
const getTurtle = async () => {
await sleep(3000);
return "거북이";
};
async function process() {
const dog = await getDog();
console.log(dog);
const rabbit = await getRabbit();
console.log(rabbit);
const turtle = await getTurtle();
console.log(turtle);
}
async function processAll() {
const results = await Promise.all([getDog(), getRabbit(), getTurtle()]);
console.log(results);
}
async function processRace() {
const first = await Promise.race([getDog(), getRabbit(), getTurtle()]);
console.log(first);
}
process(); // dog \r rabbit \r turtle
processAll(); // rabbit \r dog \r turtle
processRace(); // rabbit
npm install axios --save
혹은
yarn add axios
axios.get(url[, config])
- config는 설정을 의미한다.
axios.post(url[, data, config])
axios.put(url,data[, config])
axios.patch(url, data[, config])
axios.delete(url[, config])
aioxs는 promise를 반환한다. 이를 이용해 아래와 같이 비동기적으로 처리한다.
import axios from "axios";
axios
.get("https://api.example.com/users/1/memos")
.then((response) => {
alert("요청이 성공하였습니다.");
})
.catch((error) => {
alert("요청이 실패하였습니다.");
})
.then((response) => {
alert("성공과 실패에 관계없이 항상 실행됩니다.");
});
//ECMA 2017 async/await
async function getUserMemo() {
try {
const response = await axios.get("https://api.example.com/user/1/memos");
alert("요청이 성공하였습니다.");
} catch (error) {
alert("요청이 실패하였습니다.");
} finally {
alert("성공과 실패에 관계없이 항상 실행됩니다.");
}
}
config에 axios에 설정을 추가한다.
다양한 config 옵션들은 이 주소를 참조하자.
import axios from 'axios';
// 1. defaults 설정을 사용하면 고정된 값들을 손쉽게 사용할 수 있다.
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['X-Example-Key'] = 'example';
axios.defaults.headers.post['Content-Type'] = 'application/json'l
// 2. 서버에 따라 여러개의 모듈을 생성해야 할 때에는 create메서드를 이용해서 새로운 객체를 만들어 사용할 수 있다. 새로 만들어진 Axios 객체도 임포트된 모듈과 같은 역할을 한다.
import axios from 'axios';
const AuthAPI = axios.create({
baseURL : 'https://api.auth.com',
});
const UserAPI = axios.create({
baseURL: 'https://api.users.com',
});
// 3. 메서드에 따라서 엑시오스에 추가적인 옵션을 주입하는 방식이다.
import axios from 'axios';
axios.post('/user/1/memos', {
title: '메모 제목',
content: '메모의 내용입니다.',
}, {
headers: {'Content-Type': 'application/json'},
})
Axios의 응답은 아래와 같은 구조를 가지고 있다.
{
data: {},
status: 200,
statueText: 'OK',
config: {}, //서버로 요청을 보냈을 때 어떤 설정을 가지고 있었는지를 의미
request: {},
}
위 객체가 resolve로써 반환된다.
요청이 실패했을 경우에는 아래와 같은 Promise.reject 객체를 반환한다.
//서버에서 실패를 반환한 경우 error.response 속성은 아래와 같이 반환한다.
{
data: {},
status: ,
statusText: ' '
}
아래는 try-catch로 resolve, reject 객체를 받는 예시이다.
//서버의 응답이 없는 경우 error.response 속성이 없다. 따라서 error.request를 받아야 한다.
axios
.get("/user/1/memos")
.then((respone) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.config);
console.log(response.request);
})
.catch((error) => {
if (error.response) {
//요청을 보냈으나 서버에서 실패로 응답한 경우
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.statusText);
} else if (error.request) {
//요청은 보냈으나 서버 응답이 없어 에러가 발생한 경우, 리스폰스가 없다.
console.log(error.request);
} else {
// 이유 없이 에러가 발생한 경우도 핸들링해야 한다.
console.log("Error", error.message);
}
});
넷플릭스 클론 코딩에 사용된 방법이다.
import axios from "axios";
// 같은 요청을 여러번 반복하기 위해 baseURL, Prams, Fetch(requests.js)를 나누어 저장
const instance = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: process.env.REACT_APP_MOVIE_DB_API_KEY,
language: "ko-KR",
},
});
export default instance;
위에서 .env
파일을 최상위 폴더에 생성하였으며, REACT_APP_MOVIE_DB_API_KEY라는 식별자로 지정된 문자열을 process.env로 불러오는 모습이다.
//https://developers.themoviedb.org/
const requests = {
fetchNowPlaying: "movie/now_playing",
fetchNetflixOriginals: "/discover/tv?with_networks=213",
fetchTrending: "/trending/all/week",
fetchTopRated: "/movie/top_rated",
fetchActionMovies: "/discover/movie?with_genres=28",
fetchComedyMovies: "/discover/movie?with_genres=35",
fetchHorrorMovies: "/discover/movie?with_genres=27",
fetchRomanceMovies: "/discover/movie?with_genres=10749",
fetchDocumentaries: "/discover/movie?with_genres=99",
};
export default requests;
import axios from "../api/axios.js";
import requests from "../api/requests";
import React, { useEffect, useState } from "react";
function Row() {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetchMovieData();
});
const fetchMovieData = async () => {
const request = await axios.get(requests.fetchTrending);
setMovies(request.data.results);
};
}
export default Row;
@/api/axios.js 파일의 axios를 불러와
@/api/requests 파일의 requests.fetchTrending url과 결합하는 모습니다. axios파일에서 baseURL과 params 등을 미리 지정해두었기에 간편하게 사용이 가능하다.
import api from "@/api";
const TMDBapi = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: tmdb_api_key,
language: "ko-KR",
},
});
const WEATHERapi = axios.create({
baseURL: "https://api.openweathermap.org/data/2.5",
});
const instance = { TMDBapi, WEATHERapi };
export { TMDBapi, WEATHERapi };
export default instance;
import { TMDBapi } from "@/api";
모듈의 export와 import에 대해서는 다음을 참조하자.