동기 : 사전적으로 '동시에 일어난다' 라는 의미
프로그래밍에서 동기는 작업이 순차적으로 진행되는 것을 의미한다.
작업이 시작되면 해당 작업이 완료될 때까지 다른 작업을 기다려야 한다.
동기 방식은 호출한 함수 또는 작업이 반환될 때까지 대기하는 동안 실행 흐름이 차단되는 특징이 있다.
여러 작업이 동시에 실행되어야 하는 경우 각 작업의 완료를 기다리는 동안 시간이 소요되어 전체 프로세스의 성능이 저하될 수 있다.
한 작업이 지연되면 다른 작업들도 모두 지연되는 문제 또한 발생한다.
비동기 : 사전적으로 '동시에 일어나지 않는다'라는 의미
프로그래밍에서 비동기는 작업이 독립적으로 진행되는 것을 의미한다.
작업이 시작되면 해당 작업이 완료될 때까지 기다리지 않고 다음 코드를 실행할 수 있다.
비동기 방식은 호출한 함수 또는 작업이 완료될 때까지 기다리지 않고 즉시 다음 코드로 실행 흐름이 이동하는 특징이 있다.
비동기 방식은 Callback , Promise, async/await등의 메커니즘을 통해 구현될 수 있다.
콜백 함수는 매개변수로 다른 함수에 전달되는 함수(익명 함수)이다.
특정 동작을 완료한 후 실행되어야 하는 코드를 정의할 때 사용된다.
setTimeout()
, addEventListener()
,setInterval()
, fetch(url).then(callback)
, .json()
등
forEach()
, map()
, filter()
, reduce()
등의 배열 메서드
NOTE
콜백 함수가 동기식으로 호출되는지, 비동기식으로 호출되는지 이해하는 것은 Side effect(부수 효과)를 분석할 때 중요let value = 1; callbackFn(() => { value = 2; }); console.log(value);
- 콜백 함수가 동기적으로 실행된다면
console.log(value)
는 2를 출력할 것이고- 콜백 함수가 비동기적으로 실행된다면
console.log(value)
는 1을 출력 한다.
위NOTE
에서 봤듯이 콜백 함수는 호출 함수에 일회용으로 사용하는 경우가 많아
코드의 간결성을 위해 이름이 없는 '익명의 함수'를 사용한다.
비동기는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 방식이라고 위에서 말했다.
비동기 작업의 결과에 따라 다른 작업을 실행해야 할 때는 전통적으로 콜백 함수를 사용했다.
하지만 콜백 함수가 많아질수록 코드가 복잡해지고 관리하기 어려워 이를 더 쉽게 처리하고자 Promise
가 나오게 되었다.
바로 아래 콜백 지옥과 Promise를 활용한 개선된 비동기 처리를 코드로 확인해보자
↓↓↓
function increaseAndPrint(n, callback) {
setTimeout(() => {
const increased = n + 1;
console.log(increased);
if (callback) {
callback(increased); // 콜백함수 호출
}
}, 1000);
}
increaseAndPrint(0, n => { // (1) 0을 입력하고 비동기 실행 (1초 뒤 실행)
increaseAndPrint(n, n => { // (2) 1을 입력하고 비동기 실행 (1초 뒤 실행)
increaseAndPrint(n, n => { // (3) 2를 입력하고 비동기 실행 (1초 뒤 실행)
increaseAndPrint(n, n => { // (4) 3을 입력하고 비동기 실행 (1초 뒤 실행)
increaseAndPrint(n, n => { // (5) 4를 입력하고 비동기 실행 (1초 뒤 실행)
console.log('끝!');
});
});
});
});
});
function increaseAndPrint(n) {
return new Promise((resolve, reject)=>{
setTimeout(() => {
const increased = n + 1;
console.log(increased);
resolve(increased);
}, 1000)
})
}
increaseAndPrint(0)
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n))
.then((n) => increaseAndPrint(n))
.catch(error => { // 에러 핸들링
console.error("Something went wrong' error.message);
});
NOTE
.then()
문법을 계속 연결한 것을 체이닝 기법이라고 한다.
Promise
객체Promise
객체는 비동기 작업의 최종 완료 또는 실패를 나타내는 결과를 제공하겠다는 '약속'을 반환 한다는 의미에서 Promise라고 지어졌다.
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행
const data = fetch('URL');
if(data)
resolve(data);
else
reject("Error");
})
비동기 작업이 성공하면 resolve
를 실패하면 reject
메서드를 호출한다.
하지만 Promise
도 비동기 처리를 완벽하게 해결하지 못해 Promise 지옥(Promise Hell) 문제가 발생할 수 있었다. 이를 개선하기 위해 ES8에서는 async/await
를 도입했다.
async/await
가 도입되면서 기존 Promise
를 좀 더 편하게 사용할 수 있게 되었다.
NOTE
async/await
는Promise
를 완전하게 대체하기 위한 기능이 아닌,
단지 코드 작성 부분에서 가독성을 높이기 위한 추가 기능인 것이다.
try/catch
로 가독성 좋은 에러 핸들링async
키워드는 함수 앞에 존재한다.
// Example
async function fn() {
return 1;
}
const data = fn();
console.log(data);
NOTE
async function의 리턴 값은 항상Promise
객체이다.
await
키워드는 .then
문법을 개선한 것이다.
await
키워드는 async function
안에서만 동작한다.
async function getData() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const data = await response.json();
console.log(data);
}
↑위 코드의 처리 과정을 살펴보자
1. fetch("URL")
을 통해 Promise를 실행, response
에 응답이 오기를 기다린다.
2. response.json()
을 통해 Promise를 실행, data
를 기다린다
3. data
를 출력한다.
NOTE
.json()
은 비동기다.
async function getData() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const data = await response.json();
console.log(data);
}
위 response
에 대한 응답으로 아래와 같은 로그를 받았다.
content-type
: application/json
이라는 것과
body
: readalbe
이라는 것을 보고 해당 응답은 json으로 된, 그리고 읽을 수 있는 응답이라는 것이다.
이제 아래 try/catch
를 통한 에러 핸들링 예제를 배워보자
try/catch
문법 자체는 ES3(1993년) 부터 존재 했으나 원래는 동기적인 코드를 처리하는 용도로 사용했다.
그러나 ES8 async/await
가 도입되면서 try/catch
문을 비동기 코드에서 같이 작성할 수 있게 되었다.
.catch()
를 통한 에러 처리// then 핸들러 방식
function fetchResource(url) {
fetch(url)
.then(res => res.json()) /
.then(data => {
// data 처리
console.log(data);
})
.catch(err => {
// 에러 처리
console.error(err);
});
}
try/catch
를 통한 에러처리// async/await 방식
async function func() {
try {
const res = await fetch(url);
const data = await res.json();
// data 처리
console.log(data);
} catch (err) {
// 에러 처리
console.error(err);
}
}
func();
.then
과 .catch
체이닝을 사용하면 여러 단계의 비동기 작업을 처리할 때 코드가 중첩되어 가독성이 떨어진다.
반면
try/catch
는 코드이 가독성이 높고, 유지 보수성이 좋다. try/catch
는 하나의 블록으로 모든 에러를 처리할 수 있어서 중복된 에러 처리를 줄일 수 있다.fetch
는 JS에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 해주는 메서드다.
// Syntax
fetch("URL", option)
URL
: 요청할 서버 주소
option
:
GET
, POST
, PUT
, PATCH
, DELETE
)fetch("https://example.com/api", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: "John", age: 30 }), }) .then(// code..) .catch(// code..)
axios는 기존 fetch
를 개선한 Promise
기반의 HTTP 클라이언트로
브라우저와 Node.js 환경에서 사용 가능하다.
API 요청을 더 간결하고 직관적이게 + 쉬운 에러 처리를 할 수 있다.
NOTE
여기서 말하는 동형이란 영어로 isomorphic으로"같은 기능을 하지만 실행되는 환경이 다른"
정도로 이해하면 된다.
대표적으로useEffect
와useEffectLayout
이 동형이다
사용법은 따로 링크로 대체 (예제)