Перейти к основному содержимому
Блокчейн30ГЛАВА 30Запросы к узлу: fetch, JSON-RPC и таймаутыскриптPOST0x1263b4cузелтаймаут 500 мсобычный HTTP

Запросы к узлу: fetch, JSON-RPC и таймауты

Прочитано0%
Квизы0/5
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Предыдущая глава дала обещания и ожидание результата на выдуманных задержках. Здесь та же машинерия применяется к единственному, ради чего она в этом треке и нужна: разговору с узлом блокчейна.

Узел — обычный сервер, отвечающий на запросы по обычному сетевому протоколу. Никакой магии: тот же способ, которым программа спрашивает погоду, спрашивает и номер последнего блока.

Разберём три вещи, каждую с прогоном: как устроен разговор, почему отказ узла — это не сетевая ошибка, и почему без ограничения по времени скрипт может висеть вечно.

Запрос состоит из четырёх частей​

Метод — глагол: «отдай» или «на, обработай». Узлы принимают вызовы только вторым способом, потому что вызов надо передать телом.

Адрес — куда стучимся. У локальной сети это адрес на своей же машине с номером порта.

Заголовки — служебные пометки; главная говорит, что тело — это текст в формате обмена данными.

Тело — сам запрос: что именно спрашиваем.

const res = await fetch("http://127.0.0.1:8545", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] }),
});
const body = await res.json();
Нажми на часть выражения

Форма разговора с узлом​

Тело запроса всегда одинаковой формы — четыре поля:

ПолеЧто означает
версиякакой договорённости следуем
методчто спрашиваем: eth_blockNumber, eth_getBalance, eth_chainId
параметрысписок аргументов; часто пустой
номерчтобы сопоставить ответ с запросом

Ответ бывает ровно двух видов: с полем результата или с полем ошибки. Третьего не бывает, и это важно для следующего раздела.

Числа приходят строками — и обычное превращение их портит​

Первая модель спрашивает у локального узла три вещи:

HTTP 200 application/json
сырой ответ : {"jsonrpc":"2.0","id":1,"result":"0x1263b4c"}
номер блока : 19282764 | через BigInt: 19282764n

HTTP 200 application/json
сырой ответ : {"jsonrpc":"2.0","id":2,"result":"0x1bc16d674ec80001"}
баланс, wei : 2000000000000000001n
баланс, ETH : 2.000000000000000001
через Number: 2000000000000000000 <- потерялся один wei

HTTP 200 application/json
сеть номер : 31337

Числа приезжают строками в шестнадцатеричном виде, и это не прихоть: обычное число в формате обмена данными не вмещает баланс в мельчайших единицах. Выше определённого порога арифметика начинает врать.

Третья строка блока показывает это буквально: баланс 2000000000000000001 после обычного превращения стал 2000000000000000000. Один wei исчез молча.

Правило: суммы и балансы превращают в большие целые числа, а не в обычные. Ту же осторожность мы уже видели в Solidity, где всё считалось в мельчайших единицах.

Отказ узла — не сетевая ошибка​

Та же модель делает опечатку в имени метода:

HTTP 200 application/json
сырой ответ : {"jsonrpc":"2.0","id":4,"error":{"code":-32601,
"message":"the method eth_blockNumbr does not exist/is not available"}}
res.ok был true, сетевой ошибки не было — ошибка лежит внутри тела
код ошибки : -32601 | текст: the method eth_blockNumbr does not exist…
ДВЕ ПОРОДЫ ОТКАЗОВ, И ПУТАТЬ ИХ НЕЛЬЗЯсеть молчитузел не отвечает, порт закрытзапрос отклонён с ошибкойнастоящая причина спрятана в поле «причина»ловится обычным перехватомузел ответил и сказал «нет»HTTP 200, признак успеха: trueотказ лежит ВНУТРИ тела: код −32601перехват не сработает — исключения нетпроверяют поле ответа рукамиопечатка в имени метода — не сетевая ошибка: сеть сработала идеально, отказал узелпоэтому после каждого запроса проверяют и статус ответа, и поле ошибки в теле
Сеть сработала идеально: статус 200, признак успеха истинен — а узел отказал

Это половина главы, поэтому разберём медленно. Отказов две породы.

Сеть молчит. Узел не отвечает, порт закрыт, адрес неверен. Запрос отклоняется с ошибкой, и её ловит обычный перехват. Настоящая причина при этом прячется в поле «причина» у ошибки — само сообщение бесполезно общее.

Узел ответил и сказал «нет». Статус 200, признак успеха истинен, никакого исключения нет — а в теле лежит поле ошибки. Перехват здесь не сработает: бросать нечего.

Важно

Отсюда правило, которое стоит запомнить дословно: после каждого запроса проверяют и статус ответа, и поле ошибки в теле. Пропустите второе — и опечатка в имени метода превратится в тихую пустоту, которая всплывёт через двадцать строк.

Заодно: неуспешный статус ответа — тоже не исключение. Запрос считается выполненным, если сервер вообще ответил, каким бы ни был статус.

Отвечено вопросов: 0 из 2
В имени метода опечатка. Что вернёт запрос?
Баланс 2000000000000000001 превратили обычным способом в число. Что получилось?
Отвечено верно: 0 из 2

Подряд или вместе​

Вторая модель делает десять одинаковых запросов к серверу, который отвечает через сотню миллисекунд:

подряд : 1000 мс | пик одновременных на сервере: 1
вместе : 100 мс | пик одновременных на сервере: 10
выигрыш : 10 раз
порядок в Promise.all сохранён: результаты идут как в массиве, а не как пришли
ДЕСЯТЬ ЗАПРОСОВ ПОДРЯД ИЛИ ВМЕСТЕпо очереди, каждый ждёт предыдущего1000 мс · одновременных запросов на сервере: 1все сразу100 мс · одновременных запросов на сервере: 10выигрыш ровно в десять разклиент не стал быстрее — он перестал ждать: работу параллелит серверпорядок результатов сохраняется — они идут как в списке, а не как пришли
Клиент не стал быстрее — он перестал ждать: работу параллелит сервер

Разница в десять раз — не новость. Новость в счётчике со стороны сервера: в первом случае к нему в каждый момент стучался один запрос, во втором — десять. Клиент не ускорился; он просто перестал ждать.

Отсюда и три случая, когда так делать не надо:

  • результат нужен для следующего шага — сначала адрес, потом баланс по этому адресу: выбора нет;
  • сервер не выдержит — публичные узлы ограничивают частоту и в ответ начнут отказывать;
  • порядок важен для сети — транзакции с одного адреса нумеруются подряд, и отправлять их пачкой значит перепутать номера.

Для чтения метрик дашборда параллельность — то, что нужно. Для отправки транзакций — почти всегда нет.

Без ограничения по времени скрипт висит​

Ограничения по времени нет по умолчанию вообще. Третья модель поднимает узел, который «задумывается» на три секунды:

1. без таймаута : 0x1263b4c | ждали 3000 мс
2. с таймаутом : TimeoutError | The operation was aborted due to timeout | ждали 500 мс
3. попытка 1 : TimeoutError — The operation was aborted due to timeout
3. попытка 2 : TimeoutError — The operation was aborted due to timeout
3. попытка 3 : получилось, ответ 0x1263b4c
итого : 1600 мс на три попытки
ЗАВИСШИЙ УЗЕЛ ПОД ТРЕМЯ КЛИЕНТАМИбез таймаута3000 мс ожиданияответ пришёл — но мог не прийти никогдас таймаутом 500 мсотказ на 500-й мсошибка по таймауту, ждать пересталис повтором до трёх раз1600 мс, ответ на 3-й попыткедве первые — таймаут, третья успешнатаймаута по умолчанию нет вообще: скрипт без него висит, пока его не убьют
Три секунды ожидания могли быть вечностью: узел ответил, но мог и не ответить

Первая строка обманчиво успешна: ответ пришёл. Но узел мог не ответить никогда — и скрипт висел бы, пока его не убьют руками. На демонстрации это выглядит как «участник завис».

Ограничение задаётся сигналом прерывания:

const res = await fetch(url, { method: "POST", body, signal: AbortSignal.timeout(500) });

Третий прогон — цикл до трёх попыток с ограничением и паузой между ними. Две первые упали, третья вернула ответ; всё вместе — 1600 мс.

Это и есть рабочая заготовка для скриптов: ограничение по времени плюс несколько попыток с паузой. Узел, который поднимается в соседнем окне, отвечает не сразу — и без повтора первый же скрипт упадёт на пустом месте.

Отвечено вопросов: 0 из 2
Сколько времени запрос ждёт ответа по умолчанию?
Десять запросов отправили вместо очереди пачкой. Что изменилось на сервере?
Отвечено верно: 0 из 2

Что здесь не ловит компилятор​

Разбор тела ответа возвращает «что угодно» — и после этой строки компилятор замолкает совсем: обращение к любому вымышленному полю проходит проверку без единого замечания.

Это ровно тот случай, ради которого в главе про свои типы был unknown. Дисциплина простая: результат разбора сразу объявляют «неизвестным» и проверяют форму перед использованием. Иначе ошибка узла или смена формата ответа обнаружится падением где-то дальше по скрипту.

Типичные ошибки​

Забыто ожидание перед разбором тела. В переменной окажется обещание, а не данные.

Проверен только статус ответа. Отказ узла лежит в теле и пройдёт незамеченным.

Неуспешный статус принят за исключение. Запрос считается выполненным, если сервер ответил.

Суммы превращены в обычные числа. Тихая потеря точности на больших балансах.

Нет ограничения по времени. Скрипт висит на зависшем узле.

Нет повтора. Узел, который ещё поднимается, роняет скрипт на пустом месте.

Пачка транзакций с одного адреса разом. Номера перепутаются.

Результат разбора не типизирован. Компилятор замолкает, и ошибка формата всплывает позже.

Отсутствует заголовок с типом содержимого. Некоторые узлы отвечают отказом на такой запрос.

Слова главы​

  • Метод запроса, адрес, заголовки, тело — четыре части обращения к серверу.
  • Статус ответа — 200 успех; неуспешный статус исключения не вызывает.
  • Формат обмена данными — текст; превращение в объект и обратно — две функции.
  • Форма вызова узла — версия, метод, параметры, номер; ответ с результатом или с ошибкой.
  • Шестнадцатеричные строки — так приходят числа; суммы превращают в большие целые.
  • Две породы отказов — сеть молчит и узел сказал «нет».
  • Параллельный запуск — все запросы сразу; порядок результатов сохраняется.
  • Ограничение по времени — сигнал прерывания; по умолчанию его нет.
  • Повтор с паузой — рабочая заготовка для скриптов.

Тренажёр: набрать вслепую​

Цель: скорость ≥ 100 зн/мин, точность ≥ 88%

const res = await fetch(url, { method: "POST", headers, body });

На чемпионате​

Разговор с узлом всплывает в задании четырьмя способами, и все четыре — это код из этой главы:

  • исполняемый файл запуска решения, который поднимает частную сеть и публикует контракты: узел после старта отвечает не сразу, значит нужен повтор с паузой;
  • шаги демонстрации — каждый «показать изменения всех затронутых балансов» — это запросы и печать чисел;
  • внешняя служба ликвидации, которая по условию задания опрашивает контракт и сравнивает отношение долга к залогу: цикл опроса с ограничением по времени и повтором;
  • дашборд с метриками: там параллельные запросы дают ту самую разницу в десять раз.

Практические выводы для площадки:

  • суммы читают как большие целые числа — иначе балансы на демонстрации разойдутся с ключом эксперта в последних разрядах;
  • после каждого запроса проверяют и статус, и поле ошибки;
  • ограничение по времени ставят всегда: зависший скрипт на демонстрации выглядит хуже, чем скрипт с внятной ошибкой.
Отвечено вопросов: 0 из 2
Исполняемый файл поднимает сеть и сразу публикует контракты. Что нужно предусмотреть?
Почему балансы на демонстрации могут разойтись с ключом эксперта в последних разрядах?
Отвечено верно: 0 из 2

Что дальше​

Разговор с узлом руками разобран — и это полезно знать, потому что все инструменты делают ровно это, только прячут за удобными именами. Последняя глава блока — про то, как разложить растущий код по файлам: подключение, экспорт и устройство проекта.

Отвечено вопросов: 0 из 1
Почему числа приходят от узла строками, а не числами?
Отвечено верно: 0 из 1

Углубиться​

  • Спецификация формы вызова — три страницы, читаются за двадцать минут; там же формат пакетных запросов.
  • Каталог методов узла — что ещё можно спросить, кроме высоты и баланса.
  • Подписки на события — постоянное соединение вместо опроса: узел сам сообщает о новом блоке.
  • Ограничение частоты — как узлы защищаются от пачек запросов и что возвращают при превышении.
  • Кодирование аргументов вызова контракта — то, что инструмент из следующего блока сделает за вас.

Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоЛучший Гайд по HTTP для Начинающих за 25 Мин с НуляVlad Mishustin
ВидеоJavaScript запросы fetch — клиент-серверное взаимодействие на практикеАлександр Ламков — Friendly Frontend
ВидеоУрок 8. JavaScript. Как работает Async, Await. Работа с сервером c fetchВладилен Минин
ВидеоRPC и REST — в чём разница? Часть 1: RPCРусский Айтишник
Видео🚫 How to Cancel Fetch Requests in JavaScript | AbortController ExplainedBitBuddy

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.