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

时间:2018-08-23 06:39:01

标签: 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)
  });
}

输出

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

1 个答案:

答案 0 :(得分:12)

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

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

.then()仅一次

从本质上讲,承诺是异步的……在您的代码中,当一个承诺解决时,.then代码就被放在微任务上了吗?排队...并依次处理

当它拒绝时,因为.then没有onRejected回调,那么,您情况下的诺言链.catch中的下一个处理程序是否被添加到了微任务中?排队-但到那时,所有.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);
    });
}

另一种方法(至少在本机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)
    });
}

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

  

深入的解释

第一件事……您需要了解.then实际上是

.then(onFullfilled, onRejected)

并返回一个承诺

接下来,.catch只是“语法糖”

.then(null, onRejected)

事实上,在大多数Promise库中(在它们变成本机之前),它被定义为

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
};

对...所以让我们看一下代码的原始版本,并且为了简洁起见仅使用三个承诺

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);

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

现在,让我们对其进行一些更改,这样我们总是会获得成功/失败/成功

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);

这总是输出

*Success:* 1
*Success:* 3
*Error:* 2

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

现在,让我们以扩展形式重写.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);

我们只使用两个诺言,首先拒绝...我们知道它将先输出success 2然后输出error 1-即按照我们期望的相反顺序

所以让我们分析发生了什么

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

const p1 = new Promise(executorFunction(1, false));

立即成为已解决的Promise-对于p2实现为2,而对于p1则拒绝为1,但是它从未处于待处理状态。因此,当一个承诺没有待处理(它已经解决,但这意味着已经实现或被拒绝,但是术语已经混在一起,所以我将继续说“未处理”)时,任何“处理程序”都会添加到微任务队列中-因此,在所有这些代码的末尾,微任务队列看起来像

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

现在JS引擎由于不再运行,因此将处理微任务队列(顺便说一下,队列的头部位于底部)

  • 它看到一个被拒绝的promise(1),但是.then没有on被拒绝的功能,因此promise值会拖延链条
  • .then返回带有原始拒绝原因的被拒绝承诺
  • 此承诺,因为它已将一个处理程序(原始代码中的.catch)添加到了微任务队列中

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

现在处理下一个微任务

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

**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth
(rejected:1)>(rejected:1).then(null, error);         // added third
  • 它看到一个被拒绝的promise(1),并且有一个被拒绝的处理程序调用{​​{1}}
  • 输出error
  • error 1返回一个promise,没有处理程序,因此没有任何内容添加到微任务队列中

.then

现在处理下一个微任务

  • 它看到一个已解决的promise(2,现在未定义)-但没有onSuccess处理程序
  • **microtask queue** (resolved:2)>(resolved:undefined).then(null, error); // added fourth 返回一个promise,没有处理程序,因此没有任何内容添加到微任务队列中

.then

  

为什么使用.then(onFullfilled,onRejected)导致预期顺序

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

**microtask queue**
**empty**

微任务队列开始,就像

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);

现在处理下一个微任务

  • 它看到被拒绝的promise(1),因此调用**microtask queue** (resolved:2).then(success, error); // added second (rejected:1).then(success, error); // added first
  • 输出error
  • error 1返回一个promise,没有处理程序,因此没有任何内容添加到微任务队列中

.then
  • 它看到已解决的promise(2),因此调用**microtask queue** (resolved:2).then(success, error); // added second
  • 输出success
  • success 2返回一个promise,没有处理程序,因此没有任何内容添加到微任务队列中

.then

  

为什么要按预期顺序添加setTimeout结果

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

**microtask queue**
**empty**

为简洁起见,再次,我们只使用两个诺言,而第一个诺言失败,因为我们知道原始代码的输出为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);,然后为success 2 现在我们要考虑两个队列...微任务和“计时器”-计时器队列的优先级比微任务队列低...即,当没有什么正在运行时(即刻),JS将处理微任务队列直到它为空。甚至尝试计时器队列

所以-接下来我们要去

该代码结尾处有

fail 1

处理计时器队列,我们​​得到一个微任务** empty microtask queue ** timer queue setTimeout(resolve(2)) setTimeout(reject(1))

(rejected:1).then(success, null).then(null, error)

哦,微任务队列中有东西,让它处理并忽略计时器队列

  • 它看到一个被拒绝的promise(1),但是** microtask queue ** timer queue (rejected:1).then(success, null).then(null, error) setTimeout(resolve(2)) 没有on被拒绝的功能,因此promise值会拖延链条
  • .then返回带有原始拒绝原因的被拒绝承诺
  • 此承诺,因为它已将一个处理程序(原始代码中的.catch)添加到了微任务队列中

哦,微任务队列中有东西,让它处理并忽略计时器队列

.then
  • 它看到一个被拒绝的promise(1),并且有一个被拒绝的处理程序调用{​​{1}}
  • 输出** microtask queue ** timer queue (rejected:1).then(success, null).then(null, error) setTimeout(resolve(2))
  • error返回一个promise,没有处理程序,因此没有任何内容添加到微任务队列中

现在队列看起来像

error 1

所以,我不需要继续,因为.then在第二个promise链开始之前就已经输出了:p1