비동기 함수 (async / await)를 사용하여 콜백 또는 약속 기반 Node.js 애플리케이션을 단순화하는 방법을 학습합니다.
비동기 언어 구문은 C #의 async / await, Kotlin의 coroutines 및 Go의 goroutines와 같이 잠시 다른 언어에서 사용되었습니다. Node.js 8이 출시되면서 오랫동안 기다려온 비동기 기능도 Node.js에 포함되었습니다.
Node에서 비동기 함수 란 무엇입니까?
비동기 함수 선언은 AsyncFunction
객체를 반환 합니다. 이들은 Generator
실행이 중단 될 수 있다는 점에서 -s 와 유사합니다 . 유일한 차이점은 객체 Promise
대신 항상 a 를 반환한다는 것입니다 { value: any, done: Boolean }
. 실제로, 그들은 매우 유사하여 co 패키지를 사용하여 유사한 기능을 얻을 수 있습니다.
비동기 함수에서 Promise
거부를 유발할 수 있습니다.
약속을 가지고 구현 된 논리가 있다면
function handler (req, res) {
return request('https://user-handler-service')
.catch((err) => {
logger.error('Http error', err)
error.logged = true
throw err
})
.then((response) => Mongo.findOne({ user: response.body.user }))
.catch((err) => {
!error.logged && logger.error('Mongo error', err)
error.logged = true
throw err
})
.then((document) => executeLogic(req, res, document))
.catch((err) => {
!error.logged && console.error(err)
res.status(500).send()
})
}
다음을 사용하여 동기식 코드처럼 보이게 할 수 있습니다 async/await
.
async function handler (req, res) {
let response
try {
response = await request('https://user-handler-service')
} catch (err) {
logger.error('Http error', err)
return res.status(500).send()
}
let document
try {
document = await Mongo.findOne({ user: response.body.user })
} catch (err) {
logger.error('Mongo error', err)
return res.status(500).send()
}
executeLogic(document, req, res)
}
이전 버전의 V8에서는 처리되지 않은 약속 거부가 자동 삭제되었습니다. 이제는 Node에서 경고 메시지를받습니다. 따라서 리스너를 만들지 않아도됩니다. 그러나이 경우 오류를 처리하지 않을 때 앱을 알 수없는 상태로 만드는 경우 앱을 중단하는 것이 좋습니다.
process.on('unhandledRejection', (err) => {
console.error(err)
process.exit(1)
})
비동기 함수가있는 패턴
약속이나 콜백으로 해결할 때 복잡한 패턴이나 외부 라이브러리를 사용해야하므로 비동기 작업을 동기식으로 처리하는 기능이 매우 편리합니다.
비동기 적으로 얻은 데이터를 루프하거나 if-else
조건문을 사용해야하는 경우 입니다.
지수 적 백 오프로 다시 시도하십시오.
재시도 논리를 구현하는 것은 Promises를 사용하여 매우 서투른 작업이었습니다.
function requestWithRetry (url, retryCount) {
if (retryCount) {
return new Promise((resolve, reject) => {
const timeout = Math.pow(2, retryCount)
setTimeout(() => {
console.log('Waiting', timeout, 'ms')
_requestWithRetry(url, retryCount)
.then(resolve)
.catch(reject)
}, timeout)
})
} else {
return _requestWithRetry(url, 0)
}
}
function _requestWithRetry (url, retryCount) {
return request(url, retryCount)
.catch((err) => {
if (err.statusCode && err.statusCode >= 500) {
console.log('Retrying', err.message, retryCount)
return requestWithRetry(url, ++retryCount)
}
throw err
})
}
requestWithRetry('http://localhost:3000')
.then((res) => {
console.log(res)
})
.catch(err => {
console.error(err)
})
그것은 그것을보기 위해 나에게 두통을 주었다. 우리는 그것을 다시 작성 async/await
하고 훨씬 더 간단하게 만들 수 있습니다 .
function wait (timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, timeout)
})
}
async function requestWithRetry (url) {
const MAX_RETRIES = 10
for (let i = 0; i <= MAX_RETRIES; i++) {
try {
return await request(url)
} catch (err) {
const timeout = Math.pow(2, i)
console.log('Waiting', timeout, 'ms')
await wait(timeout)
console.log('Retrying', err.message, i)
}
}
}
중간 값
앞의 예제처럼 끔찍하지는 않지만 3 개의 비동기 함수가 서로 의존하는 경우 다음과 같은 몇 가지 추악한 솔루션 중에서 선택해야합니다.
functionA
약속은 다음 리턴functionB
그 값을 필요로하고functionC
해결 된 양의 값이 필요functionA
년대와functionB
의 약속을.
해결책 1 : .then
크리스마스 트리
function executeAsyncTask () {
return functionA()
.then((valueA) => {
return functionB(valueA)
.then((valueB) => {
return functionC(valueA, valueB)
})
})
}
이 솔루션을 통해 우리가 얻을 valueA
제 3의 주변 폐쇄에서 then
와 valueB
값으로 이전 약속에 해결합니다. 우리는 크리스마스 트리를 평평하게 할 수 없기 때문에 우리는 그 결말을 놓치고 valueA
사용할 수 없게 될 것입니다 functionC
.
엔터프라이즈 급 Node.js 개발에 대한 도움이 필요하십니까?
RisingStack의 전문가를 고용하십시오!
해결책 2 : 상위 범위로 이동
function executeAsyncTask () {
let valueA
return functionA()
.then((v) => {
valueA = v
return functionB(valueA)
})
.then((valueB) => {
return functionC(valueA, valueB)
})
}
크리스마스 트리에서는 valueA
사용할 수 있도록 더 높은 범위를 사용했습니다. 이 경우는 비슷하게 작동하지만 이제는 -s valueA
범위 밖에서 변수를 만들었 .then
으므로 해결 된 첫 번째 Promise의 값을 할당 할 수 있습니다.
이것은 확실히 작동하고, .then
사슬을 평평하게하고 의미 상으로 정확합니다. 그러나 변수 이름 valueA
이 함수의 다른 곳에서 사용되는 경우 새로운 버그를위한 방법을 열어줍니다 . 또한 같은 값에 대해 두 개의 이름 - valueA
및 v
- 을 사용해야 합니다.
해결책 3 : 불필요한 배열
function executeAsyncTask () {
return functionA()
.then(valueA => {
return Promise.all([valueA, functionB(valueA)])
})
.then(([valueA, valueB]) => {
return functionC(valueA, valueB)
})
}
valueA
Promise와 함께 배열에 전달되어 functionB
나무를 평평하게 할 수 있는 다른 이유는 없습니다 . 그들은 완전히 다른 유형 일 수 있기 때문에 배열에 전혀 속하지 않을 확률이 높습니다.
해결 방법 4 : 도우미 함수 작성
const converge = (...promises) => (...args) => {
let [head, ...tail] = promises
if (tail.length) {
return head(...args)
.then((value) => converge(...tail)(...args.concat([value])))
} else {
return head(...args)
}
}
functionA(2)
.then((valueA) => converge(functionB, functionC)(valueA))
물론 컨텍스트 저글링을 숨기려는 도우미 함수를 작성할 수는 있지만 읽는 것이 매우 어렵고 기능적 마술에 익숙하지 않은 자들에게는 이해하기 쉽지 않을 수 있습니다.
async/await
우리의 문제 를 사용함으로써 마술처럼 사라졌습니다 :
async function executeAsyncTask () {
const valueA = await functionA()
const valueB = await functionB(valueA)
return function3(valueA, valueB)
}
async / await를 사용한 여러 병렬 요청
이것은 이전과 유사합니다. 한 번에 여러 비동기 작업을 실행하고 다른 위치에서 해당 값을 사용하려는 경우 다음과 async/await
같이 쉽게 수행 할 수 있습니다 .
async function executeParallelAsyncTasks () {
const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
doSomethingWith(valueA)
doSomethingElseWith(valueB)
doAnotherThingWith(valueC)
}
앞의 예에서 보았 듯이이 값을 전달하려면이 값을 상위 범위로 이동하거나 비 의미 배열을 만들어야합니다.
배열 반복 메소드
당신은 사용할 수 있습니다 map
, filter
그리고 reduce
그들은 꽤 unintuitively 행동하지만, 비동기 기능. 다음 스크립트가 콘솔에 인쇄 할 내용을 추측 해보십시오.
- 지도
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].map(async (value) => {
const v = await asyncThing(value)
return v * 2
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
- 필터
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].filter(async (value) => {
const v = await asyncThing(value)
return v % 2 === 0
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
- 줄
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].reduce(async (acc, value) => {
return await acc + await asyncThing(value)
}, Promise.resolve(0))
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
솔루션 :
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10
iteratee의 리턴 값을 로그하면 map
우리가 기대하는 배열을 보게 될 것이다 : [ 2, 4, 6, 8 ]
. 유일한 문제는 각 값이 Promise에 의해 래핑된다는 것 AsyncFunction
입니다.
따라서 값을 가져 오려면 반환 된 배열을 다음에 전달하여 래핑을 해제해야합니다 Promise.all
.
main()
.then(v => Promise.all(v))
.then(v => console.log(v))
.catch(err => console.error(err))
원래, 먼저 모든 약속이 해결 될 때까지 기다렸다가 값을 맵핑합니다.
function main () {
return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}
main()
.then(values => values.map((value) => value * 2))
.then(v => console.log(v))
.catch(err => console.error(err))
이것은 조금 더 단순한 것 같습니다.
async/await
당신이 당신의 iteratee에 일부 긴 실행 동기 논리와 다른 장기 실행 비동기 작업이있는 경우 버전은 여전히 유용 할 수 있습니다.
이렇게하면 첫 번째 값을 얻 자마자 계산을 시작할 수 있습니다. 계산을 실행하기 위해 모든 약속이 해결 될 때까지 기다릴 필요가 없습니다. 결과가 여전히 약속에 싸여 있지만 순차적으로 그렇게했다면 훨씬 빨리 해결됩니다.
어때 filter
? 뭔가 잘못된 것이 있습니다 ...
글쎄, 당신은 짐작 [ false, true, false, true ]
했겠지요 : 반환 값은 사실이지만 약속에 싸여 원래 배열의 모든 값을 되 찾을 수 있습니다. 불행하게도이 문제를 해결하기 위해 할 수있는 일은 모든 값을 확인한 다음 필터링하는 것입니다.
축소 는 매우 간단합니다. Promise.resolve
리턴 된 누적 await
기가 랩되고 랩되어야하므로 초기 값을 감쌀 필요가 있음을 명심 하십시오 .
.. 그것은 명령형 코드 스타일에 사용되도록 상당히 명확하게 의도 되었기 때문에.
.then
체인을보다 "순수하게" 보이게 만들려면 Ramda pipeP
와 composeP
기능을 사용할 수 있습니다 .
엔터프라이즈 급 Node.js 개발에 대한 도움이 필요하십니까?
RisingStack의 전문가를 고용하십시오!
콜백 기반 Node.js 응용 프로그램 다시 작성
비동기 함수 Promise
는 기본적으로 a 를 반환 하므로 모든 콜백 기반 함수를 다시 작성하여 약속을 사용한 다음 await
그 해답 을 사용할 수 있습니다 . util.promisify
Node.js 의 함수를 사용하여 콜백 기반 함수를 Promise 기반 함수를 반환하도록 설정할 수 있습니다.
Promise 기반 응용 프로그램 다시 작성
간단한 .then
체인을 아주 간단하게 업그레이드 할 수 있으므로 async/await
즉시 사용할 수 있습니다.
function asyncTask () {
return functionA()
.then((valueA) => functionB(valueA))
.then((valueB) => functionC(valueB))
.then((valueC) => functionD(valueC))
.catch((err) => logger.error(err))
}
~로 변할 것이다
async function asyncTask () {
try {
const valueA = await functionA()
const valueB = await functionB(valueA)
const valueC = await functionC(valueB)
return await functionD(valueC)
} catch (err) {
logger.error(err)
}
}
async / await를 사용하여 Node.js 앱 다시 작성
if-else
조건문과for/while
루프 의 오래된 개념을 좋아했다면 ,try-catch
블록이 오류를 처리하는 방법 이라고 생각하면 ,
당신은 좋은 시간을 사용하여 서비스를 다시 작성해야합니다 async/await
.
지금까지 보았 듯이 여러 패턴을 훨씬 쉽게 코드하고 읽을 수 있기 때문에 Promise.then()
체인 보다 몇 가지 패턴에 더 적합 합니다. 그러나 지난 몇 년 동안 함수 프로그래밍 열풍에 빠져 있다면이 언어 기능을 전달할 수 있습니다.
'개발 > Node.JS' 카테고리의 다른 글
TypeScript 를 사용하여 Node.js 응용 프로그램 만들기 (0) | 2018.03.11 |
---|---|
Prometheus로 Node.js 성능 모니터링 (0) | 2018.03.11 |
Node Hero- Node.js 튜토리얼 시작하기 (0) | 2018.03.11 |
Node.js의 사용 - 기업에서 노드를 사용하는 방법 (0) | 2018.03.11 |
사례 연구 : Ghost에서 Node.js 메모리 누수 찾기 (0) | 2018.03.11 |