이 글에서는 주로 Node.js의 Async 및 Await 기능에 대한 관련 지식을 소개합니다. Node.js의 비동기 기능(async/await)을 사용하여 콜백이나 Promise를 단순화하는 방법을 배우게 됩니다. 참조 값. 도움이 필요한 친구가 참조할 수 있으므로 모든 사람에게 도움이 되기를 바랍니다.
C#의 async/await, Kotlin의 코루틴, go의 고루틴 등 다른 언어에는 비동기식 언어 구조가 이미 존재합니다. Node.js 8이 출시되면서 오랫동안 기다려온 비동기 기능도 기본적으로 구현되었습니다.
Node의 비동기 기능이 무엇인가요?
함수가 Async 함수로 선언되면 AsyncFunction 개체를 반환하며, 실행을 일시 중지할 수 있다는 점에서 Generator와 유사합니다. 유일한 차이점은 { value: any, done: Boolean } 객체 대신 Promise를 반환한다는 것입니다. 그래도 여전히 매우 유사하므로 co 패키지를 사용하여 동일한 기능을 얻을 수 있습니다.
비동기 함수에서는 Promise가 완료될 때까지 기다리거나 거부된 이유를 캡처할 수 있습니다.
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 버전에서는 Promise 거부가 있는 경우 거기에 있습니까? ? 처리되면 경고가 표시되며 거부 오류 리스너 함수를 만들 필요가 없습니다. 그러나 이 경우에는 애플리케이션을 종료하는 것이 좋습니다. 오류를 처리하지 않으면 애플리케이션이 알 수 없는 상태이기 때문입니다.
process.on('unhandledRejection', (err) => { console.error(err) process.exit(1) })
async 함수 패턴
비동기 작업을 다룰 때 동기 코드처럼 보이게 만드는 예가 많이 있습니다. 문제를 해결하기 위해 Promise나 콜백을 사용하는 경우 매우 복잡한 패턴이나 외부 라이브러리를 사용해야 합니다.
루프에서 비동기 데이터 수집을 사용해야 하거나 if-else 조건을 사용해야 하는 경우 매우 복잡한 상황입니다.
지수 롤백 메커니즘
Promise를 사용하여 롤백 논리를 구현하는 것은 꽤 서투릅니다.
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가 Promise를 반환하면 functionB에는 이 값이 필요하고 functioinC에는 functionA와 functionB가 완료된 후의 값이 필요합니다.
옵션 1: 그러면 크리스마스 트리
function executeAsyncTask () { return functionA() .then((valueA) => { return functionB(valueA) .then((valueB) => { return functionC(valueA, valueB) }) }) }
이 솔루션을 사용하면 세 번째 then에서 valueA와 valueB를 얻을 수 있고, 그런 다음 이전 두 then과 마찬가지로 valueA와 valueB의 값을 얻을 수 있습니다. 여기서는 크리스마스 트리를 평면화할 수 없습니다(파멸 지옥). 그렇게 하면 클로저를 잃게 되고 valueA는 functionC에서 사용할 수 없게 됩니다.
옵션 2: 상위 수준 범위로 이동
function executeAsyncTask () { let valueA return functionA() .then((v) => { valueA = v return functionB(valueA) }) .then((valueB) => { return functionC(valueA, valueB) }) }
이 크리스마스 트리에서는 더 높은 범위의 보유 변수 valueA를 사용합니다. 왜냐하면 valueA의 범위가 모든 범위 외부에 있으므로 functionC는 functionA가 완성한 첫 번째 A 값을 얻을 수 있기 때문입니다. .
이것은 .then 체인을 평탄화하기 위한 매우 "올바른" 구문이지만, 이 방법에서는 동일한 값을 유지하기 위해 두 개의 변수 valueA와 v를 사용해야 합니다.
옵션 3: 추가 배열 사용
function executeAsyncTask () { return functionA() .then(valueA => { return Promise.all([valueA, functionB(valueA)]) }) .then(([valueA, valueB]) => { return functionC(valueA, valueB) }) }
functionA의 배열을 사용하여 valueA와 Promise를 함께 반환하면 크리스마스 트리를 효과적으로 평면화할 수 있습니다(콜백 지옥).
옵션 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) }
배열 반복 방법
맵, 필터 및 축소 방법에서 비동기 함수를 사용할 수 있습니다. 비록 직관적이지 않을 수 있지만 콘솔에서 다음 코드를 실험해 볼 수 있습니다.
1.map
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))
2.filter
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))
3.reduce
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
맵 반복 데이터인 경우 반환 값이 [2, 4, 6, 8]임을 알 수 있습니다. 유일한 문제는 각 값이 AsyncFunction 함수에 의해 Promise에 래핑된다는 것입니다
그래서 값을 얻으려면 Promise.All()에 배열을 전달하여 Promise를 풀어야 합니다.
main() .then(v => Promise.all(v)) .then(v => console.log(v)) .catch(err => console.error(err)) 一开始你会等待 Promise 解决,然后使用map遍历每个值 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 버전이 여전히 유용한 경우가 많습니다.
이렇게 하면 첫 번째 값을 얻을 수 있을 때 별도의 작업 없이 일부 계산을 시작할 수 있습니다. 계산을 실행하기 전에 모든 약속이 완료될 때까지 기다려야 합니다. 결과가 Promise로 래핑되더라도 결과가 순차적으로 실행되면 더 빠릅니다.
필터에 관한 질문
你可能发觉了,即使上面filter函数里面返回了 [ false, true, false, true ] , await asyncThing(value) 会返回一个 promise 那么你肯定会得到一个原始的值。你可以在return之前等待所有异步完成,在进行过滤。
Reducing很简单,有一点需要注意的就是需要将初始值包裹在 Promise.resolve 中
重写基于callback的node应用成
Async 函数默认返回一个 Promise ,所以你可以使用 Promises 来重写任何基于 callback 的函数,然后 await 等待他们执行完毕。在node中也可以使用 util.promisify 函数将基于回调的函数转换为基于 Promise 的函数
重写基于Promise的应用程序
要转换很简单, .then 将Promise执行流串了起来。现在你可以直接使用`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) } } Rewriting Nod
使用 Async/Await 将很大程度上的使应用程序具有高可读性,降低应用程序的处理复杂度(如:错误捕获),如果你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。
相关推荐:
위 내용은 Node.js의 Async 및 Await 기능 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!