Почему стек вывода ошибки в асинхронном коде отличается от синхронного
Почему в асинхронном коде в ошибке не выводится полный стек вызовов?
Ничего не понятно, но очень интересно! (с)
Чтобы прояснить, давайте разберем на таком примере :
import fs from 'fs';
const func1 = () => {
const func2 = () => {
callUndefinedFunction();
};
fs.readFile('./directory', 'utf-8', func2);
};
func1();
Вывод ошибки будет таким:
file:///tmp/test1/index.js:5
callUndefinedFunction();
^
ReferenceError: callUndefinedFunction is not defined
at ReadFileContext.func2 [as callback] (file:///tmp/test1/index.js:5:5)
at FSReqCallback.readFileAfterOpen [as oncomplete] (node:fs:314:13)
Стек ошибки показывает на функцию func2()
, но в нём нет родительской функции func1()
вот эта строка:
at ReadFileContext.func2 [as callback] (file:///tmp/test1/index.js:5:5)
Дальше стек ошибки указывает на место в библиотечном коде: node:fs:314:13
- это мы уже не смотрим.
Теперь рассмотрим такой пример, уже с синхронным кодом:
const func2 = () => {
throw Error();
};
const func1 = () => {
func2();
};
func1();
Тут просто одна функция вложена в другую. Стек ошибки этого кода будет таким:
file:///tmp/test1/index-1.js:3
throw Error();
^
Error
at func2 (file:///tmp/test1/index-1.js:3:11)
at func1 (file:///tmp/test1/index-1.js:6:3)
at file:///tmp/test1/index-1.js:9:1
at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:385:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:61:12)
В стеке уже указываются обе функции, первые две строки:
at func2 (file:///tmp/test1/index-1.js:3:11)
at func1 (file:///tmp/test1/index-1.js:6:3)
Дальше идут строки из внутреннего кода ноды. По этому выводу мы видим стек ошибки, где на верхнем уровне стека функция func2()
, внутри которой и была ошибка, ниже идёт func1()
, внутри которой была вызвана функция func2()
. То есть в стеке обе наши функции. В первом же примере в стеке нет функции func1()
, там сразу на первом уровне стека идёт ReadFileContext.func2 (это по сути и есть наша функция-колбек func2()
). Метод fs.readFile()
вызвался, создал новый стек и поместил в этот стек колбек. Именно поэтому в нём нет func1()
.
Все стало понятно! Существует механизм стека вызова в котором интерпретатор пушит функции в стек по принципу сначала все сложил, потом по одной сверху вытащил Разница между асинхронными и синхронными функциями заключена в контексте выполнения этих самых функций и порядке выполнения Функция func1 была вызвана и оказалась в стеке [func1], затем была вызвана вторая функция func2 и оказалась в стеке [func2, func1] далее мы видимо в консоли ошибки
- at func2 (file:///tmp/test1/index-1.js:3:11)
- at func1 (file:///tmp/test1/index-1.js:6:3) Это стек вызовов + сонктекст синхронных функций
Асинхронная функция создает свой контекст выполнения в котором была вызвана функция fs.readFile() которая принимает параметром callback, а именно функцию func 1 Получается, что у асинхронной функции свой контекст выполнения
Можно схематично показать этот стек вызовов [func1(), readFile()] - один стек Но разные контексты выполнения
Получается что синхронная функция вызванная как колллбэк асинхронной функцией находится в контексте выполенения асинхронной функции Из-за этого и отличается стек вывода ошибок
Согласен с предыдущим комментатором
Метод fs.readFile() вызвался, создал новый стек и поместил в этот стек колбек. Именно поэтому в нём нет func1().
Вот, эту деталь я не особо улавливал. Спасибо!