为什么JS承诺先打印所有的解析然后拒绝第二个

Ten*_*ang 6 javascript es6-promise

为什么承诺首先打印所有成功然后是拒绝后,即使我为其编写代码随机出现

var errors = 0;
var successes = 0;
var p1;
for (var i = 0; i < 10; i++) {
  p1 = new Promise(function(resolve, reject) {
    var num = Math.random();
    if (num < .5) {
      resolve(num);
    } else {
      reject(num)
    }
  });

  p1.then(function success(res) {
    successes++;
    console.log("*Success:* " + res)
  }).catch(function error(error) {
    errors++
    console.log("*Error:* " + error)
  });
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT

VM331:16 *Success:* 0.28122982053146894
VM331:16 *Success:* 0.30950619874924445
VM331:16 *Success:* 0.4631742111936423
VM331:16 *Success:* 0.059198322061176256
VM331:16 *Success:* 0.17961879374514966
VM331:16 *Success:* 0.24027158041021068
VM331:19 *Error:* 0.9116586303879894
VM331:19 *Error:* 0.7676575145407345
VM331:19 *Error:* 0.5289135948801782
VM331:19 *Error:* 0.5581542856881132
Run Code Online (Sandbox Code Playgroud)

Jar*_*a X 12

它与异步代码的工作方式有关

.then().catch() - 必须等待队列两次(嗯,我需要解释一下)

.then() 只有一次

Promise本质上是异步的...在你的代码中,当一个promise解析时,.then代码放在microtask上?队列...并依次处理

当它拒绝,因为.then没有onRejected回调,那么,.catch你的情况下的promise链中的下一个处理程序是否被添加到微任务中?队列 - 但到那时,所有.then代码都已执行

尝试使用.then(onSuccess, onError),你会得到你期望的

var errors = 0;
var successes = 0;
var p1;
for (var i = 0; i < 10; i++) {
    p1 = new Promise(function(resolve, reject) {
        var num = Math.random();
        if (num < .5) {
            resolve(num);
        } else {
            reject(num);
        }
    });
    p1.then(function success(res) {
        successes++;
        console.log("*Success:* " + res);
    }, function error(error) {
        errors++;
        console.log("*Error:* " + error);
    });
}
Run Code Online (Sandbox Code Playgroud)

获得你想要的东西的另一种方式(至少在原生的Promises中)是

var errors = 0;
var successes = 0;
var p1;
for (let i = 0; i < 10; i++) {
    p1 = new Promise(function(resolve, reject) {
      setTimeout(() => {
            var num = Math.random();
            if (num < .5) {
                resolve(`${i} ${num}`);
            } else {
                reject(`${i} ${num}`)
            }
        });
    });
    p1.then(function success(res) {
        successes++;
        console.log("*Success:* " + res)
    }).catch(function error(error) {
        errors++
        console.log("*  Error:* " + error)
    });
}
Run Code Online (Sandbox Code Playgroud)

这是因为setTimeout会延迟解析/拒绝

深入解释

首先要做的事情......你需要了解.then实际情况

.then(onFullfilled, onRejected)
Run Code Online (Sandbox Code Playgroud)

并返回一个Promise

接下来,.catch简直就是"语法糖"

.then(null, onRejected)
Run Code Online (Sandbox Code Playgroud)

实际上,在大多数Promise库中(在它们出生之前)它被定义为

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
};
Run Code Online (Sandbox Code Playgroud)

是的......所以让我们来看看你的代码的一个简单的简单版本 - 为了简洁,只使用三个承诺

function executorFunction(resolve, reject) {
    const num = Math.random();
    if (num < .5) {
      resolve(num);
    } else {
      reject(num)
    }
}
let successes = 0, errors = 0;
function success(res) {
    successes++;
    console.log("*Success:* " + res)
}
function error(error) {
    errors++
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction);
p1.then(success).catch(error);

const p2 = new Promise(executorFunction);
p2.then(success).catch(error);

const p3 = new Promise(executorFunction);
p3.then(success).catch(error);
Run Code Online (Sandbox Code Playgroud)

您可以运行它并看到它产生相同的成功和错误顺序

现在,让我们稍微改变一下,这样我们总能获得成功/失败/成功

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, false));
p1.then(success).catch(error);

const p2 = new Promise(executorFunction(2, true));
p2.then(success).catch(error);

const p3 = new Promise(executorFunction(3, false));
p3.then(success).catch(error);
Run Code Online (Sandbox Code Playgroud)

这总是输出

*Success:* 1
*Success:* 3
*Error:* 2
Run Code Online (Sandbox Code Playgroud)

所以我们看到你在问题中看到的顺序 - 到目前为止一切顺利

现在,让我们以扩展形式重写.then/.catch

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, true));
p1.then(success, null).then(null, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, null).then(null, error);
Run Code Online (Sandbox Code Playgroud)

让我们只使用两个承诺,第一个拒绝......我们知道这将输出success 2error 1-即以相反的顺序,我们期待

那么让我们来分析一下发生了什么

因为您在Promise构造函数中同步解析/拒绝 executorFunction

const p1 = new Promise(executorFunction(1, false));
Run Code Online (Sandbox Code Playgroud)

是一个已经解决的Promise - 对于p2已经达到2,并且对于p1被拒绝,原因为1,但它永远不会处于Pending状态.所以,当一个承诺没有待处理(它已经解决,但这意味着要么已经完成或被拒绝,但是术语已经混淆了,所以我会继续说"没有待定"),任何"处理程序"都被添加到微任务队列 - 所以在所有代码的末尾,微任务队列看起来像

**microtask queue**
(resolved:2).then(success, null).then(null, error); // added second
(rejected:1).then(success, null).then(null, error); // added first
Run Code Online (Sandbox Code Playgroud)

现在JS引擎,因为没有任何运行,处理微任务队列(顺便说一下队列的头部在底部)

  • 它看到一个被拒绝的承诺(1),但是.then没有被拒绝的函数,所以承诺值带有链
  • .then 以原始拒绝原因返回此被拒绝的承诺
  • 这个承诺,因为它有一个处理程序(原始代码中的.catch)被添加到微任务队列中

.

**microtask queue**
(rejected:1)>(rejected:1).then(null, error);         // added third
(resolved:2).then(success, error).then(null, error); // added second
Run Code Online (Sandbox Code Playgroud)

现在处理下一个微任务

  • 它看到一个已解决的承诺(2)所以调用 success
  • 输出 success 2
  • .then返回一个promise,因为你的success函数没有返回,这就是return undefined并且promise被解决为undefined
  • 这个承诺,因为它有一个处理程序(原始代码中的.catch)被添加到微任务队列中

.

**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth
(rejected:1)>(rejected:1).then(null, error);         // added third
Run Code Online (Sandbox Code Playgroud)
  • 它看到一个被拒绝的promise(1)并且有一个onrejected处理程序调用 error
  • 输出 error 1
  • .then 返回一个promise,没有处理程序,因此没有任何东西被添加到microtask队列中

.

**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth
Run Code Online (Sandbox Code Playgroud)

现在处理下一个微任务

  • 它看到了一个已解决的承诺(2,现在未定义) - 但是没有onSuccess处理程序
  • .then 返回一个promise,没有处理程序,因此没有任何东西被添加到microtask队列中

.

**microtask queue**
**empty**
Run Code Online (Sandbox Code Playgroud)

为什么使用.then(onFullfilled,onRejected)会产生预期的顺序

好的,现在,如果我们编写代码

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}
const p1 = new Promise(executorFunction(1, true));
p1.then(success, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, error);
Run Code Online (Sandbox Code Playgroud)

微任务队列开始,就像

**microtask queue**
(resolved:2).then(success, error); // added second
(rejected:1).then(success, error); // added first
Run Code Online (Sandbox Code Playgroud)

现在处理下一个微任务

  • 它看到了被拒绝的承诺(1)所以要求 error
  • 输出 error 1
  • .then 返回一个promise,没有处理程序,因此没有任何东西被添加到microtask队列中

.

**microtask queue**
(resolved:2).then(success, error); // added second
Run Code Online (Sandbox Code Playgroud)
  • 它看到一个已解决的承诺(2)所以调用 success
  • 输出 success 2
  • .then 返回一个promise,没有处理程序,因此没有任何东西被添加到microtask队列中

.

**microtask queue**
**empty**
Run Code Online (Sandbox Code Playgroud)

为什么添加setTimeout会导致预期的顺序

现在让我们更改executorFunction以添加setTimeout

function executorFunction(num, fail) {
    return (resolve, reject) => {
        setTimeout(function() {
            if (fail) {
              reject(num);
            } else {
              resolve(num)
            }
        });
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, true));
p1.then(success, null).then(null, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, null).then(null, error);
Run Code Online (Sandbox Code Playgroud)

同样,为了简便起见,我们仅使用两个承诺,第一个失败了,因为我们知道,在原码输出会success 2那么fail 1 现在我们有两个队列考虑...... microtask和"计时器" -定时器队列中有比微任务队列更低的优先级...即,当没有运行(立即)时,JS将处理微任务队列,直到它为空,甚至在尝试计时器队列之前

所以 - 我们走了

在我们的代码结束时

** empty microtask queue **                             timer queue
                                                        setTimeout(resolve(2))
                                                        setTimeout(reject(1))
Run Code Online (Sandbox Code Playgroud)

处理计时器队列,我们​​得到一个微任务 (rejected:1).then(success, null).then(null, error)

** microtask queue **                                   timer queue
(rejected:1).then(success, null).then(null, error)      setTimeout(resolve(2))
Run Code Online (Sandbox Code Playgroud)

哦,微任务队列中有一些东西,让我们处理它并忽略定时器队列

  • 它看到一个被拒绝的承诺(1),但是.then没有被拒绝的函数,所以承诺值带有链
  • .then 以原始拒绝原因返回此被拒绝的承诺
  • 这个承诺,因为它有一个处理程序(原始代码中的.catch)被添加到微任务队列中

哦,微任务队列中有一些东西,让我们处理它并忽略定时器队列

** microtask queue **                                   timer queue
(rejected:1).then(success, null).then(null, error)      setTimeout(resolve(2))
Run Code Online (Sandbox Code Playgroud)
  • 它看到一个被拒绝的promise(1)并且有一个onrejected处理程序调用 error
  • 输出 error 1
  • .then 返回一个promise,没有处理程序,因此没有任何东西被添加到microtask队列中

现在队列看起来像

** microtask queue **                                   timer queue
                                                        setTimeout(resolve(2))
Run Code Online (Sandbox Code Playgroud)

因此,我不需要继续,因为error 1在第二个承诺链开始之前已经输出了:p1