동기 방식과 비동기 방식
코드를 실행할때 순차적으로 또는 비순차적으로 실행하냐에 따라 동기적 비동기적으로 나뉜다.
1. 동기적 처리(Synchronous)
위에서 아래로 코드가 순서대로 실행이 되는 것을 동기적 처리라고 한다. 즉 지금 진행하는 작업이 끝이나면 다른 작업으로 넘어가고 그 작업이 끝이 나면 다른 작업으로 넘어가는 방식이 동기적 처리 방식이라고 한다.
2. 비동기적 처리(Asynchronous)
비동기적 처리 작업은 순서대로 진행하는 것이 아니라 한번에 여러개가 진행되는 것과 마찬가지다. 비동기적 처리는 주로 api요청, 파일읽기, 암호화, 복호화 등에서 자주 사용된다. 비동기적 처리를 사용하는데 주로 사용되는 방식으로는 1. callback 함수 2. promise 객체 3. async/await가 존재한다.
동기 방식(Synchronous)
코드가 작성한 순서대로 실행되는 방식
예시
아래의 코드는 동기적 코드이므로 순차적으로 실행된다
a 함수가 먼저 실행된 후에 b 함수가 실행된다
function a() {
console.log('A')
}
function b() {
console.log('B')
}
a()
b()
b 함수가 먼저 실행된 후에 a 함수가 실행된다
function a() {
console.log('A')
}
function b() {
console.log('B')
}
b()
a()
비동기 방식(Asynchronous)
코드가 작성한 순서대로 실행되지 않는 방식
비동기 코드의 필요성
아래의 로직은 함수 a에 setTimeout이라는 메서드로 1초의 지연시간을 주었기 때문에 는 b 함수가 먼저 실행된 후 a 함수가 실행될 것이다.
function a() {
setTimeout(() => {
console.log('A')
}, 1000)
}
function b() {
console.log('B')
}
a()
b()
만약 함수 a가 데이터를 받아오는 로직을 가지고 있어 시간지연이 발생하고 b 함수는 반드시 a 함수가 데이터를 받아온 뒤에 실행해야한다면 어떻게 해야할까? 이때 비동기 방식을 도입해 a 함수와 b 함수가 동시에 실행되지 않고 b 함수가 a 함수의 실행이 끝날때까지 대기하도록 만들어준다.
function a() {
// 데이터를 백엔드에서 받아오는데 1초의 시간이 걸림
}
function b() {
// 데이터를 받아온 후에 실행되야하는 함수
}
a()
b()
자바스크립트 코드를 비동기적인 방식으로 실행하고 싶을때 사용할 수 있는 방법
- callback(동기, 비동기 둘다 가능)
- promise
- async, await
1. callback
callback이란?
특정 로직이 끝난 후 새로운 로직이 실행되는 것을 보장하기 위해 (비동기적으로 코드를 실행하기 위해) callback 함수를 사용한다
콜백함수란 함수의 파라미터로 함수를 넘겨주고 이를 내부에서 호출하는 것을 말한다 즉 하나의 데이터처럼 사용되는 함수를 말한다
// 함수 a의 파라미터로 또다른 함수를 받아와 안에서 실행시켜준다
function a(callback) {
console.log('A')
callback()
}
// 함수 a의 인자로 함수 b를 사용한다
// 함수의 인자로 또다른 함수를 사용한다
a(function b() {
console.log('Done!')
})
예시
아래의 코드에서 함수a의 인수로 또다른 함수 b를 가지고 있는 콜백함수의 형태를 띄고 있다
콜백함수를 사용함으로서 a 함수가 실행된 후에 b 함수가 실행되는 것을 보장한다
function a(callback) {
setTimeout(() => {
console.log('A')
callback()
}, 1000)
}
function b() {
console.log('B')
}
a(function() {
b()
})
콜백함수를 실행하면 a 함수가 실행한 뒤 b 함수가 실행된다는 사실을 함수 실행코드로 직관적으로 알 수 있다
a(function() {
b()
})
콜백지옥
예를 들어 a 함수, b 함수, c 함수, d 함수가 모두 데이터를 받아와 시간 지연이 발생되는 함수이고 a, b, c, d 순서로 실행되어야 한다면 아래와 같이 작성할 수 있을 것이다.
function a(callback) {
setTimeout(() => {
console.log('A')
callback()
}, 1000)
}
function b(callback) {
setTimeout(() => {
console.log('B')
callback()
}, 1000)
}
function c(callback) {
setTimeout(() => {
console.log('C')
callback()
}, 1000)
}
function d(callback) {
setTimeout(() => {
console.log('D')
callback()
}, 1000)
}
a(function() {
b(function() {
c(function() {
d(function() {
console.log('Done!')
})
})
})
})
함수를 a, b, c, d 순서대로 실행하기 위해 작성된 코드를 보면 구조가 깊어지면서 가독성이 떨어짐을 알 수 있다. 이와 같이 콜백이 여러번 발생하면서 구조가 깊어지는 현상을 콜백지옥이라고 한다.
a(function() {
b(function() {
c(function() {
d(function() {
console.log('Done!')
})
})
})
})
콜백지옥의 문제점
1. 가독성이 떨어진다(한눈에 로직을 이해하기가 어렵다)
2. 디버깅이 어렵다
3. 유지보수가 어렵다
콜백지옥의 문제를 개선하기 위해 나타난 새로운 문법
→ promise의 등장
2. promise
프러미스(Promise)란?
1. 비동기처리에서 흔하게 사용되는 콜백은 여러번 중첩될 경우 콜백지옥의 문제점이 생긴다
2. 프러미스는 콜백을 대신해 비동기처리에 쓰이는 객체이다
3. ECMAScript 2015 (ES6) 문법이다
프러미스의 2가지 시나리오
1. 정해진 시간에 수행했을 경우 → 성공의 메시지와함께 처리된 결과값을 전달
2. 예상치 못한 문제가 발생할경우 → 에러를 전달
Promise의 두가지 인자
1. resolve: Promise가 정상적으로 수행 시 resolve를 리턴한다
2. reject: Promise에 에러가 발생하는 경우에는 reject를 반환한다
function func() {
return new Promise((resolve, reject) => {
resolve()
reject()
})
}
Promise의 세가지 상태
1. 대기(pending): 이행하거나 거부되지 않은 초기상태
2. 이행(fulfilled): 연산이 성공적으로 완료됨
3. 거부(rejected): 연산이 실패함
function func() {
// 대기(pending)
return new Promise((resolve, reject) => {
// 이행(fulfilled)
resolve()
// 거부(rejected): 연산이 실패함
reject()
})
}
Promise의 세가지 메서드 (예외처리)
1. then: then은 Promise가 정상적으로 수행했을 경우 resolve 콜백함수를 통해 전달받은 값을 파라미터로 전달받는다.
2. catch: catch는 Promise가 에러 발생 시 reject 콜백함수를 통해 전달받은 값을 파라미터로 전달받는다.
3. finally: finally는 Promise의 결과와 상관없이 마지막에 무조건 실행된다.
promise
.then(value => {
console.log(value) // resolve 값 전달
})
.catch(error => {
console.log(error) // reject 값 전달
})
.finally(() => {
console.log('finally') // 결과에 상관없이 무조건 전달
})
promise
.then(() => {
console.log('resolve') // resolve() 함수가 실행된 후 실행할 로직을 작성
})
.catch(() => {
console.log('reject') // reject() 함수가 실행된 후 실행할 로직을 작성
})
.finally(() => {
console.log('finally') // 결과에 상관없이 무조건 전달
})
예시
예시 1) Promise 사용법
아래의 코드에서 함수 a는 Promise 객체를 반환한다
함수 a가 Promise 객체를 반환한 값을 가지고 Promise의 메서드인 then을 실행한다
then 메서드를 사용함으로서 a 함수가 실행된 후에 b 함수가 실행되는 것을 보장한다
function a() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('A')
resolve()
}, 1000)
})
}
function b() {
console.log('B')
}
function test() {
a().then(() => {
b()
})
}
test()
콜백함수를 실행하면 a 함수가 실행한 뒤 b 함수가 실행된다는 사실을 함수 실행코드로 직관적으로 알 수 있다
function test() {
a().then(() => {
b()
})
}
예시 2) Promise 중첩 사용
예를 들어 a 함수, b 함수, c 함수, d 함수가 모두 데이터를 받아와 시간 지연이 발생되는 함수이고 a, b, c, d 순서로 실행되어야 한다면 아래와 같이 작성할 수 있을 것이다.
function a() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('A')
resolve('Hello A')
}, 1000)
})
}
function b() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('B')
resolve('Hello B')
}, 1000)
})
}
function c() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('C')
resolve('Hello C')
}, 1000)
})
}
function d() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('D')
resolve('Hello D')
}, 1000)
})
}
function test() {
a().then(() => {
b().then(() => {
c().then(() => {
d().then(() => {
console.log('Done!')
})
})
})
})
}
test()
콜백지옥처럼 Promise도 여러번 중첩적으로 사용하면 코드가 깊어지면서 가독성이 떨어진다.
function test() {
a().then(() => {
b().then(() => {
c().then(() => {
d().then(() => {
console.log('Done!')
})
})
})
})
}
return 키워드를 사용해 코드가 깊어지는 현상을 방지해준다.
function test() {
a().then(() => {
return b()
}).then(() => {
return c()
}).then(() => {
return d()
}).then(() => {
console.log('Done!')
})
}
화살표 함수에서 중괄호와 return 키워드를 생략해 간결하게 만들어준다.
function test() {
a()
.then(() => b())
.then(() => c())
.then(() => d())
.then(() => console.log('Done'))
}
예시 3) Promise의 예외처리
Promise의 인자로 resolve, reject가 들어간다
Promise가 정상적으로 수행 시 resolve를 리턴하고 Promise에 에러가 발생하는 경우에는 reject를 리턴한다
const promise = new Promise((resolve, reject) => {
// 비동기적으로 실행하고 싶은 코드를 작성
console.log('doing something...')
setTimeout(() => {
resolve('Success') // Promise State: fulfilled 완료
reject(new Error('No network')) // Promise State: rejected 실패
}, 1000)
})
아래의 코드에서는 특정 조건(number > 4)에 따라 Promise 객체가 resolve 또는 reject 함수를 실행한다
number가 4 이상이면 reject() 함수가 실행되면서 catch() 함수가 실행된다
number가 4미만이면 resolve() 함수가 실행되면서 then() 함수가 실행된다
function a(number) {
return new Promise((resolve, reject) => {
if (number > 4) {
reject()
return
}
setTimeout(() => {
console.log('A')
resolve()
}, 1000)
})
}
function test() {
a(7)
.then(() => {
console.log('resolve')
})
.catch(() => {
console.log('reject')
})
}
test()
3. async, await
async, await란?
- 비동기 처리방식인 콜백함수와 프러미스의 단점을 보완하기 위해 2017년에 나온 자바스크립트의 비동기 처리 문법
- 콜백함수가 여러번 중첩되면 콜백 지옥이 발생 → 프러미스 사용
- 프러미스가 여러번 중첩되면 콜백지옥과 같은 문제점이 발생 → async & await을 사용해서 마치 동기적으로 코드가 진행되는 것처럼 사용한다
- 함수 앞에 async라는 키워드를 붙여주면 함수 블럭안에 있는 코드가 자동으로 promise를 사용하는 것처럼 변환이 되어진다
- async라는 키워드가 붙은 함수내에서만 await을 사용할 수 있다
async, await의 예외처리
async function func() {
try {
await 프러미스 객체를 반환하는 함수()
// resolve() 함수가 실행하면 사용할 로직
} catch(error) {
// reject() 함수가 실행하면 사용할 로직
} finally {
// 결과에 상관없이 반드시 사용할 로직
}
}
Promise 라는 객체가 반환되면 async await 키워드를 붙일 수 있다 await 키워드를 가진 함수는 Promise 객체에서 resolve 함수가 실행될때까지 기다린다
function a() {
return new Promise((resolve) => {
// 시간이 걸리는 로직
resolve()
})
}
async function test() {
// 함수 a는 반환된 Promise 객체를 가지고 resolve라는 함수가 호출되기 전까지 기다린다
await a()
// 함수 a가 실행된 후 실행할 로직을 작성한다
}
예시
예시 1) axios 사용
axios.get() 메서드로 받아온 데이터를 비동기적으로 처리해주기
function data() {
return new Promise((resolve) => {
// 전달해줄 데이터 생성
resolve('전달해줄 데이터')
})
}
async function fetchData() {
// axios의 get 메서드로 데이터를 비동기적으로 받아온다
// 이때 받아올 데이터는 resolve 함수 안에 있다
const res = await axios.get('API 주소')
// 함수 fetchData가 실행된 후 실행할 로직을 작성한다
console.log(res) // '전달해줄 데이터'
}
function a() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('A')
resolve('Hello A')
}, 1000)
})
}
function b() {
console.log('B')
}
async function test() {
const res = await a()
console.log('res: ', res)
b()
}
test()
예시 2) async, await 중첩 사용
function a() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('A')
resolve('Hello A')
}, 1000)
})
}
function b() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('B')
resolve('Hello B')
}, 1000)
})
}
function c() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('C')
resolve('Hello C')
}, 1000)
})
}
function d() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('D')
resolve('Hello D')
}, 1000)
})
}
async function test() {
const h1 = await a()
const h2 = await b()
const h3 = await c()
const h4 = await d()
console.log('Done!')
console.log(h1, h2, h3, h4)
}
test()
예시 3) try, catch문으로 예외처리 해주기
function a(number) {
return new Promise((resolve, reject) => {
if (number > 4) {
reject()
return
}
setTimeout(() => {
console.log('A')
resolve()
}, 1000)
})
}
async function test() {
try {
await a(7)
console.log('resolve')
} catch(error) {
console.log('reject')
}
}
test()'JavaScript' 카테고리의 다른 글
| [자바스크립트] 객체 구조분해할당시 key 이름 다시 지정해주기 (0) | 2022.08.10 |
|---|---|
| [자바스크립트] 이벤트 총정리 (이벤트 종류, 이벤트 핸들러와 리스너, 버블링과 캡처링을 이용한 이벤트 위임) (0) | 2022.08.09 |
| [자바스크립트] Lodash를 사용해서 배열 안에 같은 value를 가진 객체 데이터 삭제해주기 (데이터 고유화 작업) (0) | 2022.08.07 |
| [자바스크립트/한줄정리] 화살표 함수 생략 방법 (0) | 2022.07.08 |
| [자바스크립트] addEventListener의 두번째 인자로 왜 함수 실행이 아닌 함수 자체를 가질까? (0) | 2022.06.23 |
댓글