/
Вопросы и ответы
/
JavaScript
/

Почему стек вывода ошибки в асинхронном коде отличается от синхронного

Почему стек вывода ошибки в асинхронном коде отличается от синхронного

Почему в асинхронном коде в ошибке не выводится полный стек вызовов?

3 года назад

Ivan Gagarinov

Ответы

30

Ничего не понятно, но очень интересно! (с)

2 года назад

Егор Курилко

27

Чтобы прояснить, давайте разберем на таком примере :

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().

3 года назад

Ivan Gagarinov

7

Все стало понятно! Существует механизм стека вызова в котором интерпретатор пушит функции в стек по принципу сначала все сложил, потом по одной сверху вытащил Разница между асинхронными и синхронными функциями заключена в контексте выполнения этих самых функций и порядке выполнения Функция func1 была вызвана и оказалась в стеке [func1], затем была вызвана вторая функция func2 и оказалась в стеке [func2, func1] далее мы видимо в консоли ошибки

  • at func2 (file:///tmp/test1/index-1.js:3
    )
  • at func1 (file:///tmp/test1/index-1.js:6
    ) Это стек вызовов + сонктекст синхронных функций

Асинхронная функция создает свой контекст выполнения в котором была вызвана функция fs.readFile() которая принимает параметром callback, а именно функцию  func 1 Получается, что у асинхронной функции свой контекст выполнения

Можно схематично показать этот стек вызовов [func1(), readFile()] - один стек Но разные контексты выполнения

Получается что синхронная функция вызванная как колллбэк асинхронной функцией находится в контексте выполенения асинхронной функции Из-за этого и отличается стек вывода ошибок

2 года назад

Кирилл Аксенов

2

Метод fs.readFile() вызвался, создал новый стек и поместил в этот стек колбек. Именно поэтому в нём нет func1().

Вот, эту деталь я не особо улавливал. Спасибо!

2 года назад

Игорь Шипилов

1

Согласен с предыдущим комментатором

2 года назад

Александр Курсаков