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

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

Аватар пользователя Ivan Gagarinov
Ivan Gagarinov
28 сентября 2022

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

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

17 0
Аватар пользователя Егор Курилко
Егор Курилко
22 сентября 2023

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

16 0
Аватар пользователя Кирилл
Кирилл
16 февраля 2024

Все стало понятно! Существует механизм стека вызова в котором интерпретатор пушит функции в стек по принципу сначала все сложил, потом по одной сверху вытащил Разница между асинхронными и синхронными функциями заключена в контексте выполнения этих самых функций и порядке выполнения Функция 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()] - один стек Но разные контексты выполнения

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

1 0
Аватар пользователя Игорь Шипилов
Игорь Шипилов
20 декабря 2023

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

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

0 0

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

0 0
Основы Frontend за 14 дней
  • 72 урока в онлайн-тренажере
  • 4 живых вебинара и масскодинг
  • Помощь наставника на весь период обучения

Есть что добавить? Зарегистрируйтесь

или войдите в аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Курсы по программированию в Хекслете

Backend-разработка

Разработка серверной части сайтов и веб-приложений

Frontend-разработка

Разработка внешнего интерфейса сайтов и веб-приложений и верстка

Создание сайтов

Разработка сайтов и веб-приложений на JS, Python, Java, PHP и Ruby on Rails

Тестирование

Ручное тестирование и автоматизированное тестирование на JS, Python, Java и PHP

Аналитика данных

Сбор, анализ и интерпретация данных на Python

Интенсивные курсы

Интенсивное обучение для продолжающих

DevOps

Автоматизация настройки локального окружения и серверов, развертывания и деплоя

Веб-разработка

Разработка, верстка и деплой сайтов и веб-приложений, трудоустройство для разработчиков

Математика для программистов

Обучение разделам математики, которые будут полезны при изучении программирования

JavaScript

Разработка сайтов и веб-приложений и автоматизированное тестирование на JS

Python

Веб-разработка, автоматическое тестирование и аналитика данных на Python

Java

Веб-разработка и автоматическое тестирование на Java

PHP

Веб-разработка и автоматическое тестирование на PHP

Ruby

Разработка сайтов и веб-приложений на Ruby on Rails

Go

Курсы по веб-разработке на языке Go

HTML

Современная верстка с помощью HTML и CSS

SQL

Проектирование базы данных, выполнение SQL-запросов и изучение реляционных СУБД

Git

Система управления версиями Git, регулярные выражения и основы командой строки