Это классный вопрос, чтобы разобраться в нем.
Когда вы это сделаете:
verifier(3,4).then(...)
который возвращает новое обещание, для которого требуется еще один цикл возврата к циклу событий, прежде чем это вновь отклоненное обещание сможет запустить .catch()следующий обработчик. Этот дополнительный цикл дает следующую последовательность:
verifier(5,4).then(...)
шанс запустить его .then()обработчик перед предыдущей строкой, .catch()потому что он уже был в очереди до того, как .catch()обработчик из первой попадет в очередь, а элементы запускаются из очереди в порядке FIFO.
Обратите внимание, что если вы используете .then(f1, f2)форму вместо .then().catch(), она запускается тогда, когда вы этого ожидаете, потому что нет дополнительных обещаний и, следовательно, дополнительных тиков:
const verifier = (a, b) =>
new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));
verifier(3, 4)
.then((response) => console.log("response (3,4): ", response),
(error) => console.log("error (3,4): ", error)
);
verifier(5, 4)
.then((response) => console.log("response (5,4): ", response))
.catch((error) => console.log("error (5,4): ", error));
Обратите внимание, я также пометил все сообщения, чтобы вы могли видеть, из какого verifier()вызова они приходят, что значительно упрощает чтение вывода.
Спецификация ES6 для заказа обратного вызова обещаний и более подробное объяснение
Спецификация ES6 сообщает нам, что «задания» обещаний (поскольку они вызывают обратный вызов из .then()или .catch()) выполняются в порядке FIFO в зависимости от того, когда они вставлены в очередь заданий. Он конкретно не называет FIFO, но указывает, что новые задания вставляются в конец очереди, а задания запускаются с начала очереди. Это реализует порядок FIFO.
PerformPromiseThen (который выполняет обратный вызов из .then()) приведет к EnqueueJob, что является тем, как запланировано фактическое выполнение обработчика разрешения или отклонения. EnqueueJob указывает, что ожидающее задание добавляется в конец очереди заданий. Затем операция NextJob извлекает элемент из начала очереди. Это обеспечивает порядок FIFO при обслуживании заданий из очереди заданий Promise.
Итак, в примере в исходном вопросе мы получаем обратные вызовы для verifier(3,4)обещания и verifier(5,4)обещания, вставленного в очередь заданий в том порядке, в котором они были запущены, потому что оба этих исходных обещания выполнены. Затем, когда интерпретатор возвращается в цикл обработки событий, он сначала берет verifier(3,4)задание. Это обещание отклонено, и в verifier(3,4).then(...). Таким образом, он отклоняет обещание, которое verifier(3,4).then(...)вернулось, и в результате verifier(3,4).then(...).catch(...)обработчик вставляется в jobQueue.
Затем он возвращается в цикл обработки событий, и следующее задание, которое он извлекает из jobQueue, - это verifier(5, 4)задание. У него есть обработанное обещание и обработчик разрешения, поэтому он вызывает этот обработчик. Это приводит response (5,4):к отображению вывода.
Затем он возвращается в цикл обработки событий, и следующее задание, которое он извлекает из jobQueue, - это verifier(3,4).then(...).catch(...)задание, в котором оно выполняется, и это приводит error (3,4)к отображению вывода.
Это потому, что .catch()в 1-й цепочке на один уровень обещания глубже, чем .then()во 2-й цепочке, которая вызывает упорядочивание, о котором вы сообщили. И это потому, что цепочки обещаний переходят с одного уровня на другой через очередь заданий в порядке FIFO, а не синхронно.
Общие рекомендации относительно использования этого уровня детализации планирования
К вашему сведению, в общем, я стараюсь писать код, который не зависит от этого уровня подробных знаний о времени. Хотя это любопытно и иногда полезно понимать, это хрупкий код, поскольку простое, казалось бы, безобидное изменение кода может привести к изменению относительного времени. Итак, если время между двумя подобными цепочками имеет решающее значение, я бы предпочел написать код таким образом, чтобы он задавал время так, как я хочу, чем полагаться на этот уровень детального понимания.