В какой момент в вызове функции есть запрос AJAX, инициированный браузером?

Скажем, у меня есть функция, которая выполняет стандартный запрос AJAX:

function doXHR() {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts');
  xhr.send();
  xhr.onreadystatechange = () => {
    console.log('ready state change');
  };
}

doXHR();
console.log('done');

Этот код заставит браузер запустить запрос AJAX, но в какой момент функция действительно запускает запрос? Согласно этому сообщению: https://blog.raananweber.com/2015/06/17/no-there-are-no-race-conditions-in-javascript/

[Вызов xhr.onreadystate() после send()] возможен, потому что запрос HTTP выполняется только после завершения текущей задачи. Это позволяет программисту устанавливать обратные вызовы в любой позиции, которую он пожелает. Поскольку JavaScript является однопоточным, запрос может быть отправлен только тогда, когда один единственный поток может запускать новые задачи. HTTP-запрос добавляется в список задач, которые должны выполняться, которые управляются движком.

Но когда я добавляю точку останова в devtools сразу после отправки вызова:

введите описание изображения здесь

Я получаю сетевой запрос, хотя в ожидающем состоянии:

введите описание изображения здесь

В точке останова XHR readystate равно 1, что равно XMLHttpRequest.OPENED. Согласно документации MDN, XMLHttpRequest.OPENED означает, что был вызван xhr.open(): https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState

Но если я прокомментирую xhr.send() так, что вызывается только .open():

введите описание изображения здесь

Тогда я не получаю ожидающий запрос по сети:

введите описание изображения здесь

Думая, что, возможно, мне нужна область функций для завершения, чтобы запрос действительно был отправлен, я переместил точку останова после вызова функции (и немного изменил код, чтобы увидеть XHR readyState):

введите описание изображения здесь

Но я все еще получаю ожидающий сетевой запрос:

введите описание изображения здесь

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

Мой вопрос: в какой момент запрос действительно отправляется? Позволяет ли вызов xhr.send() просто настроить запрос, но требует, чтобы область действия объекта заканчивалась до начала запроса? Выполняет ли xhr.send() запрос перед тем, как заканчивается область действия функции? Или здесь что-то еще происходит?

Ответ 1

send немедленно инициирует запрос. Это, по крайней мере, намечено документация MDN для send.

Автор блога, на который вы ссылаетесь, является правильным, что условия гонки не существуют, но это не мешает вам иметь вещи, которые не соответствуют вашим требованиям. Примером этого может быть, если вы загрузите несколько тегов <script> с помощью async=true, установленного на них. В этом случае, если один script зависит от другого, вы можете оказаться в ситуации, когда у вас есть непредсказуемая последовательность событий (что очень похоже на состояние гонки), потому что два асинхронных события заканчиваются в разное время.

Верно, что вы можете установить onreadystatechange после вызова send, потому что даже если запрос запроса не удался немедленно, событие не будет отправлено до завершения функциональной области. Задержка здесь не в отправке сетевого запроса, а в диспетчеризации события, чтобы сказать, что запрос сети завершен.

Важно отметить, что сама сеть не обрабатывается в JavaScript, а скорее в результате реализации браузера, который является собственным кодом и может быть многопоточным (хотя я не знаю, это). Это означает, что браузер отлично способен обрабатывать сетевые задачи во время работы вашего javascript.