В этой статье вы узнаете, как использовать асинхронные функции (async/await) в Node.js для упрощения обратных вызовов или промисов.
Структуры асинхронных языков уже существуют в других языках, таких как async/await в C#, сопрограммы в Kotlin и горутины в go.С выпуском Node.js 8 долгожданная асинхронная функция также реализована по умолчанию.
Что такое асинхронная функция в Node?
Когда функция объявлена как асинхронная, она возвращаетAsyncFunction
предметы, похожие наGenerator
Потому что исполнение может быть приостановлено. Разница лишь в том, что они возвращаютсяPromise
вместо{ value: any, done: Boolean }
объект. Они все еще очень похожи, вы можете использоватьcopackage для получения той же функциональности.
В асинхронной функции вы можете ждатьPromise
Заполните или зафиксируйте причину отклонения.
Если вы хотите реализовать свою собственную логику в Promises
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)
})
шаблон асинхронной функции
При работе с асинхронными операциями есть много примеров, которые делают их похожими на работу с синхронным кодом. При использованииPromise
илиcallbacks
Для решения проблемы требуется использование очень сложных паттернов или внешних библиотек.
Используйте данные асинхронной выборки, когда вам нужно переработать или использовать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:then
Рождественская елка
function executeAsyncTask () {
return functionA()
.then((valueA) => {
return functionB(valueA)
.then((valueB) => {
return functionC(valueA, valueB)
})
})
}
С этим решением мы в третьемthen
доступно вvalueA
а такжеvalueB
, то можно перейти к первым двумthen
получить то же самоеvalueA
а такжеvalueB
ценность . Здесь нет способа сплющить елку (испортить к черту), вы потеряете замыкание, если вы это сделаете,valueA
существуетfunctioinC
Он будет недоступен.
Сценарий 2: переход к предыдущей области
function executeAsyncTask () {
let valueA
return functionA()
.then((v) => {
valueA = v
return functionB(valueA)
})
.then((valueB) => {
return functionC(valueA, valueB)
})
}
В этой рождественской елке мы используем переменные с более высокой областью видимости.valueA
,потому чтоvalueA
размах во всемthen
выходит за рамки, поэтомуfunctionC
получить первыйfunctionA
завершенное значение.
Это очень эффективное сглаживание.then
chain "правильный" синтаксис, однако в этом методе нам нужно использовать две переменныеvalueA
а такжеv
для хранения одного и того же значения.
Вариант 3. Используйте избыточный массив
function executeAsyncTask () {
return functionA()
.then(valueA => {
return Promise.all([valueA, functionB(valueA)])
})
.then(([valueA, valueB]) => {
return functionC(valueA, valueB)
})
}
в функцииfunctionA
изthen
используя массив в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)
}
Метод итерации массива
ты сможешьmap
,filter
,reduce
Метод использует асинхронные функции, хотя они могут показаться не очень интуитивными, вы можете поэкспериментировать со следующим кодом в консоли.
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
Решите, затем используйте карту для перебора каждого значения
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
Не запускайте расчеты, пока не закончите. Хотя результат завернут вPromise
, но результаты будут быстрее, если выполняться последовательно.
оfilter
Эта проблема
Возможно, вы заметили, что даже если приведенная выше функция фильтра возвращает[ false, true, false, true ]
,await asyncThing(value)
вернетpromise
Тогда вы обязательно получите примитивное значение. Вы можете дождаться завершения всех асинхронных операций перед возвратом во время фильтрации.
ReducingОчень просто, стоит отметить, что вам нужно обернуть начальное значение вPromise.resolve
середина
Перепишите приложение узла на основе обратного вызова, чтобы
Async
По умолчанию функция возвращаетPromise
, так что вы можете использоватьPromises
переопределить любой на основеcallback
функция, тоawait
Подождите, пока они закончат выполнение. также может использоваться в узле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
Это значительно сделает приложение более читабельным и уменьшит сложность обработки приложения (например, захват ошибок).Если вы также используете версию узла v8+, вы можете попробовать ее, и могут быть новые преимущества.
Если есть ошибка, пожалуйста, оставьте сообщение и позвольте мне исправить его, спасибо за чтение