Возвращение промисов
Короткое объяснение
У v8 есть особая способность, называемая "бесплатные асинхронные стектрейсы", которая позволяет стектрейсам не
обрываться на самом позднем await
. Но, из-за нетривиальных нюансов реализации, она не сработает если возвращаемое
значение функци и (синхронной или асинхронной) является промис. По этому, для того чтобы избежать дыр в стектрейсах
после отказа (rejection) возвращаемого промиса, следует всегда явно разрешать (resolve) промисы при помощи await
перед тем как возвращать их из функций
Анти-паттерн №1: return promise
Javascript
async function throwAsync(msg) {
await null // нужно выполнить await для того что бы функция была по-настоящему асинхронной (см. заметку №2)
throw Error(msg)
}
async function returnWithoutAwait () {
return throwAsync('missing returnWithoutAwait in the stacktrace')
}
// 👎 returnWithoutAwait будет отсутствовать в стектрейсе
returnWithoutAwait().catch(console.log)
выведет в лог
Error: missing returnWithoutAwait in the stacktrace
at throwAsync ([...])
Как правильно: return await promise
Javascript
async function throwAsync(msg) {
await null // нужно выполнить await для того что бы функция была по-настоящему асинхронной (см. заметку №2)
throw Error(msg)
}
async function returnWithAwait() {
return await throwAsync('with all frames present')
}
// 👍 returnWithAwait будет присутствовать в стектрейсе
returnWithAwait().catch(console.log)
выведет в лог
Error: with all frames present
at throwAsync ([...])
at async returnWithAwait ([...])
Анти-паттерн №2: синхронная функция возвращающая промис
Javascript
async function throwAsync () {
await null // нужно выполнить await для того что бы функция была по-настоящему асинхронной (см. заметку №2)
throw Error('missing syncFn in the stacktrace')
}
function syncFn () {
return throwAsync()
}
async function asyncFn () {
return await syncFn()
}
// 👎 syncFn будет отсутствовать в стектрейсе так как она синхронная и возвращает промис
asyncFn().catch(console.log)
would log
Error: missing syncFn in the stacktrace
at throwAsync ([...])
at async asyncFn ([...])
Как правильо: объявить функцию возвращающую промис как асинхронную
Javascript
async function throwAsync () {
await null // нужно выполнить await для того что бы функция была по-настоящему асинхронной (см. заметку №2)
throw Error('with all frames present')
}
async function changedFromSyncToAsyncFn () {
return await throwAsync()
}
async function asyncFn () {
return await changedFromSyncToAsyncFn()
}
// 👍 теперь changedFromSyncToAsyncFn будет присутствовать в стектрейсе
asyncFn().catch(console.log)
would log
Error: with all frames present
at throwAsync ([...])
at changedFromSyncToAsyncFn ([...])
at async asyncFn ([...])
Анти-паттерн №3: прямая передача асинхронного коллбэка в месте где ожидается синхронный коллбек
Javascript
async function getUser (id) {
await null
if (!id) throw Error('stacktrace is missing the place where getUser has been called')
return {id}
}
const userIds = [1, 2, 0, 3]
// 👎 хотя в стектрейсе будет присутствовать функция getUser, в нем не будет места где она была вызвана
Promise.all(userIds.map(getUser)).catch(console.log)
выведет в лог
Error: stacktrace is missing the place where getUser has been called
at getUser ([...])
at async Promise.all (index 2)
Между прочим: может показаться что Promise.all (index 2)
может помоч понять где getUser
была вызвана, но из-за
совершенно другого бага в v8, (index 2)
является строкой из
внутреннего кода v8