- callback / promise / (async / await)
- 5초, 10초, 3초 걸리는 일을 순차처리, 병행처리 했을때 코드 및 걸리는 시간
- 5초, 10초, 3초 걸리는 일이 있다. 이 일을 순차처리하는 코드를 작성하라. 단, 일을 하는 것은 아래의 이름으로 작성하고, setTimout으로 처리한다.
processJob(seconds, callback), processJob(seconds)
- 첫번재 버전은 callback형태로 처리하는 방법, 두번째 버전은 Promise를 사용, 세번재 버전은 async / await 을 사용한다.
일정한 시간 뒤에 처리가 완료되는 로직을 사용하기 위해 setTimeout API를 이용한다.
요약
비동기 순차적 실행
비동기 병렬적 실행
callback을 사용하여 비동기적인 작업을 병렬적으로 진행하기 위해서 async.parallel을 사용한다.
Promise나 async/await을 사용할 때는 Promise.all을 사용할 수 있다. Promise.all은 pending되어 있는 프로미스 객체들로 이루어진 배열이나 객체를 받아 병렬적으로 실행시키고, 그 결과값을 배열로 리턴해준다.
[x] 순차처리
[x] callback
async/waterfall 사용 버전
import async from "async"
const processJob = (seconds, callback) => {
if (typeof seconds !== "number") {
reject(new Error("Seconds argument has to be number type."))
}
setTimeout(function () {
callback(null, `finished ${seconds}`)
}, seconds * 1000)
}
const mainFunc = () => {
console.time("main function")
async.waterfall(
**[
(cb) => {
processJob(5, cb)
},
(data, cb) => {
console.log(data)
processJob(10, cb)
},
(data, cb) => {
console.log(data)
processJob(3, cb)
},
],**
(err, data) => {
if (err) throw err
**console.log(data)**
console.timeEnd("main function")
}
)
}
mainFunc()
// 18초
총체적 난국
task
각각의 task는 맨 첫 task를 제외하고는 바로 앞의 task에서 넘겨준 데이터를 인자로 활용할 수 있다.
task가 바로 앞 task의 데이터와 콜백 함수를 받도록 하고 그 안에서 processJob을 실행시키는 방식으로 코드를 작성할 수 있다.
1차
변경 이전(callback hell)
에러 : 콜백함수 미스
에러 : 콜백함수 호출 관련
[x] promise
const processJob = (seconds) => {
return new Promise((resolve, reject) => {
if (typeof seconds !== "number") {
reject(new Error('Seconds argument has to be number type.'))
}
setTimeout(() => {
const result = `finished ${seconds}`
resolve(result)
},
seconds * 1000
)
})
}
const mainFunc = () => {
console.time()
processJob(5)
.then((result) => {
console.log(result)
return processJob(10)
})
.then((result) => {
console.log(result)
return processJob(3)
})
.then((result) => {
console.log(result)
console.timeEnd()
})
.catch(err => {
alert(err)
})
}
mainFunc()
에러 처리 하기 전
Promise 객체에서의 resolve, reject 인자
resolve() 안에 console.log()를 넣어야 할까?
만약 return processJob이 아니라 그냥 processJob을 실행만 시켜준다면?
[x] async/await
const processJob = (seconds) => {
return new Promise((resolve, reject) => {
if (typeof seconds !== "number") {
reject(new Error('Seconds argument has to be number type.'))
}
setTimeout(() => {
const result = `finished ${seconds}`
resolve(result)
},
seconds * 1000
)
})
}
const mainFunc = async() => {
console.time()
try {
const res5 = await processJob(5)
console.log(res5)
const res10 = await processJob('10')
console.log(res10)
const res3 = await processJob(3)
console.log(res3)
} catch(err) {
console.log(err)
}
console.timeEnd()
}
mainFunc()
[x] 병행처리
[x] callback
콜백이 아닌 그냥 순차적 실행이므로 혹시 콜백을 사용해서 진행할 수 있을지 고민해 보아야 함.
async.parallel 사용 시
import async from "async"
const processJob = (seconds, callback) => {
if (typeof seconds !== "number") {
callback(new Error("Seconds argument has to be number type."))
return
}
setTimeout(function () {
const data = `finished ${seconds}`
console.log(data)
callback(null, data)
}, seconds * 1000)
}
const mainFunc = () => {
console.time()
async.parallel(
**[
(cb) => processJob(5, cb),
(cb) => processJob(10, cb),
(cb) => processJob(3, cb),
]**,
(err, results) => {
if (err) throw err
console.log(results)
console.timeEnd()
}
)
}
mainFunc()
2차 개선 전 (bind 사용하여 외부 데이터 받음)
task
처리해주려 하는 비동기 작업이 들어간다. 일반적으로는 인자로 callback을 받고 이 callback을 로직 안에서 수행해줌으로써 자신이 건네줄 데이터를 마지막 callback 함수에 보내준다.
callback
task의 함수들이 인자로 받고 리턴할 값을 callback(에러값 null, return값)
이렇게 콜백에 인자로 넣어주면, 비동기 작업이 모두 완료된 뒤에 리턴값들을 모아 하나의 배열로 반환해준다. 이것이 parallel 맨 뒤의 콜백 함수에 인자로 들어가는 results 배열 데이터이다.
위의 코드에서는 모든 비동기 작업이 끝나면 마지막 콜백 함수가 인자로 results를 받아 console.log에 출력해주는데, 그 값은 [ 'finished 5', 'finished 10', 'finished 3' ]
가 된다.
async 라이브러리 사용 전
[x] promise
processJob 결과값 출력 로직 분리
Promise.all 코드 끝났을 때 리턴하는 promise 객체로 넘겨줌
→ 모든 병렬 로직이 끝났을 때가 되어서야 result 값이 출력이 됨
→ 각각의 로직이 끝날 때마다 결과값 보여주기 위해서는 어떻게 해야 할까?
const processJob = (seconds) => {
return new Promise((resolve, reject) => {
if (typeof seconds !== "number")
reject(new Error("Seconds argument has to be number type."))
setTimeout(() => {
const result = `finished ${seconds}`
resolve(result)
}, seconds * 1000)
})
}
const mainFunc = () => {
console.time()
**const arr = [5, 10, 3].map((seconds) => {
return processJob(seconds)** // pending 상태의 프로미스들 리턴 => 배열로 Promise.all에 전달
**})**
Promise.all(arr).then((results) => {
console.log(results)
console.timeEnd()
})
}
mainFunc()
[x] async/await
Promise.all 역시 프로미스를 리턴하므로 await을 사용하여 병렬적으로 처리할 수 있다.
const processJob = (seconds) => {
return new Promise((resolve, reject) => {
if (typeof seconds !== "number")
reject(new Error("Seconds argument has to be number type."))
setTimeout(() => {
const result = `finished ${seconds}`
resolve(result)
}, seconds * 1000)
})
}
const mainFunc = async () => {
console.time()
**const arr = [5, 10, 3].map((seconds) => processJob(seconds))**
// 함수 실행 후 pending 상태 promise로 구성된 array
const results = await Promise.all(**arr**)
console.log(results)
console.timeEnd()
}
mainFunc()
// default: 10001.01611328125 ms
오류 우선 콜백(error-first callback) 사용
nodeJS v6의 다양한 예시들 확인해보기(https://nodejs.org/dist/latest-v6.x/docs/api/dns.html#dns_dns_resolve_hostname_rrtype_callback)
promise와 async/await 에러도 처리해보기