Essa é uma pergunta legal para se chegar ao fundo.
Quando você faz isso:
verifier(3,4).then(...)
que retorna uma nova promessa que requer outro ciclo de volta ao loop de eventos antes que a promessa rejeitada possa executar o .catch()manipulador a seguir. Esse ciclo extra dá a próxima sequência:
verifier(5,4).then(...)
a chance de executar seu .then()manipulador antes da linha anterior .catch()porque ele já estava na fila antes que o .catch()manipulador da primeira entre na fila e os itens sejam executados da fila em ordem FIFO.
Observe que, se você usar o .then(f1, f2)formulário no lugar do .then().catch(), ele será executado quando você espera, porque não há promessa adicional e, portanto, nenhuma marca adicional envolvida:
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));
Observe, também rotulei todas as mensagens para que você possa ver de qual verifier()chamada elas vêm, o que torna muito mais fácil ler a saída.
ES6 Especificação sobre pedido de retorno de chamada de promessa e explicação mais detalhada
A especificação ES6 nos diz que os "trabalhos" de promessa (já que chama um retorno de chamada de um .then()ou .catch()) são executados em ordem FIFO com base em quando são inseridos na fila de trabalhos. Ele não nomeia especificamente FIFO, mas especifica que novos trabalhos são inseridos no final da fila e os trabalhos são executados a partir do início da fila. Isso implementa o pedido FIFO.
PerformPromiseThen (que executa o retorno de chamada de .then()) levará a EnqueueJob, que é como o manipulador de resolução ou rejeição é programado para ser executado de fato. EnqueueJob especifica que o trabalho pendente é adicionado no final da fila de trabalhos. Em seguida, a operação NextJob puxa o item da frente da fila. Isso garante ordem FIFO em trabalhos de serviço da fila de trabalhos Promise.
Portanto, no exemplo da pergunta original, obtemos os retornos de chamada para a verifier(3,4)promessa e a verifier(5,4)promessa inserida na fila de tarefas na ordem em que foram executadas porque ambas as promessas originais foram cumpridas. Então, quando o intérprete volta ao loop de eventos, ele primeiro pega o verifier(3,4)trabalho. Essa promessa foi rejeitada e não há retorno de chamada para isso no verifier(3,4).then(...). Portanto, o que ele faz é rejeitar a promessa que verifier(3,4).then(...)retornou e que faz com que o verifier(3,4).then(...).catch(...)manipulador seja inserido no jobQueue.
Em seguida, ele volta ao loop de eventos e o próximo trabalho que extrai de jobQueue é o verifier(5, 4)trabalho. Isso tem uma promessa resolvida e um manipulador de resolução, portanto, ele chama esse manipulador. Isso faz com que a response (5,4):saída seja exibida.
Em seguida, ele volta para o loop de eventos e o próximo trabalho que puxa do jobQueue é o verifier(3,4).then(...).catch(...)trabalho onde é executado e isso faz com que a error (3,4)saída seja mostrada.
É porque .catch()na 1ª cadeia há um nível de promessa mais profundo em sua cadeia do que .then()na 2ª cadeia que causa a ordem que você relatou. E é porque as cadeias de promessa são percorridas de um nível para o próximo por meio da fila de tarefas na ordem FIFO, não de forma síncrona.
Recomendação geral sobre confiar neste nível de detalhe de programação
Para sua informação, em geral, tento escrever um código que não dependa desse nível de conhecimento detalhado de temporização. Embora seja curioso e ocasionalmente útil de entender, é um código frágil, pois uma mudança simples e aparentemente inócua no código pode levar a uma mudança no tempo relativo. Portanto, se o tempo for crítico entre duas cadeias como essa, prefiro escrever o código de uma forma que force o tempo da maneira que desejo, em vez de confiar nesse nível de compreensão detalhada.