我看zakas的understandinges6上面的例子实际运行出来的结果跟zakas讲的结果不一样,比如下面这个例子:
"use strict";
var p1 = new Promise((resolve, reject) => {
resolve("this is p1");
});
var p2 = Promise.reject(2);
var p3 = new Promise((resolve, reject) => {
resolve("this is p3");
});
var p4 = Promise.race([p1, p2, p3]);
p4
.then(
(value) => {
console.log(value);
},
(error) => {
console.log(error);
}
);
结果是:
this is p1
也就是说p4的fulfillment handler被调用了。
按照zakas讲的(我觉得zakas讲的没错啊),应该是p2初始就是rejected状态的,所以导致p4立即变成rejected,然后调用p4的rejection handler.
但现在却是p1抢先一步变成了fulfilled,然后导致p4变成fulfilled了。
1
df4VW 2015-07-25 12:08:22 +08:00
The Promise.race(iterable) method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.
[p1, p2, p3].next() => p1 |
2
haozhang OP @df4VW you can change [p1, p2, p3] into [p2, p1, p3] , and the result is puzzling.
|
3
yoa1q7y 2015-07-25 12:10:32 +08:00
|
4
oott123 2015-07-25 12:13:12 +08:00
p1 在创建的时候就被执行了,并不一定等到你 race 才开始。
|
8
oott123 2015-07-25 12:24:44 +08:00
|
9
haozhang OP @oott123 测试结果是这样的...但是zakas讲的不是这样的...zakas认为p2改变了p4,而不是p1改变了p4.
|
11
haozhang OP @df4VW 你说的是one of the promises in the iterable resolves or rejects , 哪里说按照数组的顺序来了。
|
12
otakustay 2015-07-25 14:22:12 +08:00 1
从我现在为止对Promise的认识来说,Zakas说的是错的,只能简单地表达下
Promise.reject的流程大概是这样的: 1. Let C be the this value. 2. Let promiseCapability be NewPromiseCapability(C). 3. ReturnIfAbrupt(promiseCapability). 4. Let rejectResult be the result of calling the [[Call]] internal method of promiseCapability.[[Reject]] with undefined as thisArgument and (r) as argumentsList. 5. ReturnIfAbrupt(rejectResult). 6. Return promiseCapability.[[Promise]]. 1-3都是同步的操作,无非就是建一个叫PromiseCapability的东西出来,重点在第4步 第4步说的是调用PromiseCapability上一个叫[[Reject]]的方法,这个方法是什么,可以从规范里找出来。规范写得也是略复杂,总之找来找去,这个方法对应的是PromiseCapability Records这个东西上的[[Reject]]属性,它的解释是“The function that is used to reject the given promise object.” 然后继续往下找,可以在《Promise Reject Functions》这一节找到这个方法的说明: 1. Assert: F has a [[Promise]] internal slot whose value is an Object. 2. Let promise be the value of F's [[Promise]] internal slot. 3. Let alreadyResolved be the value of F's [[AlreadyResolved]] internal slot. 4. If alreadyResolved.[[value]] is true, then return undefined. 5. Set alreadyResolved.[[value]] to true. 6. Return RejectPromise(promise, reason). 上面这6步的重点显然在第6点,RejectPromise这个动作的说明是: 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending". 2. Let reactions be the value of promise's [[PromiseRejectReactions]] internal slot. 3. Set the value of promise's [[PromiseResult]] internal slot to reason. 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined. 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined. 6. Set the value of promise's [[PromiseState]] internal slot to "rejected". 7. Return TriggerPromiseReactions(reactions, reason). 重点在第7步,TriggerPromiseReactions: 1. Repeat for each reaction in reactions, in original insertion order 1.1. Perform EnqueueTask("PromiseTasks", PromiseReactionTask, (reaction, argument)). 2. Return undefined. 这里看到EnqueueTask就不用往下看了,这个操作是异步的,往任务队列里放一个任务来调用then/catch挂上来的回调 上面这个过程,从RejectPromise开始和new Promise(executor)是一模一样的,所以说白了new Promise和Promise.reject其实一样,到TriggerPromiseReactions为止挂上异步任务 所以new Promise(executor)里直接resolve和Promise.resolve是一个概念,都是“当前Promise的状态变了,所有回调加到任务队列”,那后面到底哪个会影响p4就完全看遍历顺序了 |
13
haozhang OP @otakustay 我写了以下的代码
```javascript "use strict"; var p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("this is p1"); }, 2000); }); var p2 = Promise.reject(2); var p3 = new Promise((resolve, reject) => { resolve("this is p3"); }); var p4 = Promise.race([p1, p2, p3]); p4 .then( (value) => { console.log("p4 fulfilled", value); }, (error) => { console.log("p4 rejected", error); } ); ``` 结果是: p4 rejected 2 也证明了你所说的。对于在帖子顶部的那段代码,我想是因为p1、p2、p3的状态都已经改变了,而明显p1的状态是最先改变的,因此p4的状态也随着p1改变了。 而一旦我在p1的exceutor里面延时一下,此时p2、p3的状态都改变了,但是p2的状态是最先改变的,因此p4的状态也随着p2改变了。 |
14
otakustay 2015-07-25 20:06:53 +08:00 1
@haozhang 你观察的结果是对的,但你说的原理是不对的……今天身体不大舒服,明天记得的话可以at我一下我再来详细说这个事儿,蛮复杂的- -
|
18
otakustay 2015-07-26 19:45:18 +08:00
@haozhang 微博和GitHub都是otakustay
我来说下为啥上面 @haozhang 说的原理是错的。这个问题最根本的一个关键在于:race(或者all等其它和Promise有关的方法)的决定因素不是“Promise状态的变化”,而是“callback的执行”,所以“p1的状态是最先改变”这样的说法完全没用,比如我搞这样的代码: var p1 = new Promise(function (resolve) { setTimeout(resolve, 1000); }); var p2 = Promise.reject(); setTimeout(function () { var p3 = Promise.race(p1, p2); p3.then(logResolved, logRejected); }, 1500); 这代码里p3会进入resolved状态,但p1显然“比p2更晚改变状态” 所以再重复一次核心:决定因素是callback的执行 那么其原理要研究的就是,callback是啥时候执行的 在上面我贴规范的时候已经有提到一个词,叫enqueueTask。简单来说,就是浏览器有一个全局的任务队列,我们把一个个任务放到队列里,浏览器从队列里拿出来一个执行完再去拿下一个,每个任务的执行都是异步的 当然浏览器其实不一定只有一个队列,任务也不止一种,Promise产生的任务和setTimeout产生的就不一样,这种细节的事不展开说 然后还需要研究的是,在Promise执行过程中,什么时候执行enqueuTask。对于Promise来说也很简单,当then/catch被调用的时候,形成2种可能性: 1. Promise还没有resolve或reject,即处在pending状态,此时会把callback存下来,等进入某个状态时再把对应的callback一一enqueuTask 2. Promise本身已经在resolve或reject状态,则把给到的callback(当然要符合当前状态)直接enqueueTask 到这里这个逻辑其实就很简单了,主楼的代码分析下来是这样的: 1. 创建p1,此时任务队列为空,[] 2. 由于执行new Promise时给的executor函数,p1进入resolved状态,但注意此时并没有任何的callback,所以没有东西执行enqueueTask,队列还是空,[] 3. 创建p2、p3,不多说了,和p1一样,关键就是他们状态虽然是改变了,但根本没有任何callback,所以到此时为止队列依旧是空的,[] 4. 创建p4,用的Promise.race,race方法会分别给传入的Promise们调用then添加callback。所以这一段代码会依次**按顺序**给p1、p2、p3添加resolve和reject的回调,因为3个Promise状态都已经是确定的,所以会执行3次enqueueTask,队列变成[因p1导致p4变resolve, 因p2导致p4变reject, 因p3导致p4变resolve],这3个任务任意一个执行就会导致p4状态变化 5. 给p4挂上2个callback,不过此时p4状态没有改变还是pending,所以这2个callback不会enqueueTask 6. 这段代码执行完了,浏览器算是完成了一个任务,要去获取下一个任务,所以从队列中拿出第1个,即“p1导致p4变resolve”,队列变成2个任务,[因p2导致p4变reject, 因p3导致p4变resolve] 7. 执行拿出来的任务,于是p4被resolve了,且对应的value是p1里的“this is p1”,resolve的同时会把关联的callback放进队列,所以此时队列变成3个任务,[因p2导致p4变reject, 因p3导致p4变resolve, console.log(value)] 8. 继续拿下一个任务,拿出来“因p2导致p4变reject”并执行,但很遗憾p4现在已经是resolve状态了,所以这个任务执行了啥也不会发生,此时队列变成2个任务,[因p3导致p4变resolve, console.log(value)] 9. 下一个任务不多说了,反正p4是不会再变状态了,于是队列里还剩最后一个任务,[console.log(value)] 10. 把这最后一个任务拿出来执行,于是打印出来“this is p1” JavaScript里凡遇到异步的问题,拿个笔画这个任务队列是最好的理思路的手段 |
19
otakustay 2015-07-26 19:46:09 +08:00
v2ex评论不支持markdown对这类技术上的讨论确实很不友好,使用gist虽然能输入代码,但第一不能内联代码段,第二大量的小片段的代码用gist也很影响观看体验
|
21
haozhang OP @otakustay 我的理解是:Promise.race()和Promise.all()方法看的是,谁先调用race或者all添加的回调函数,也就是race/all产生的promise对象的状态是根据谁先调用回调函数来判断的(这个回调函数指的是race/all添加的回调函数),根据这个理解,我仿写了一个race函数,你看看我的理解对不对:
var race = (array) => { return new Promise((resolve, reject) => { array.map((cur) => { cur .then( (value) => { resolve(value); }, (error) => { reject(error); } ); }); }); }; |
22
haozhang OP <script src="https://gist.github.com/Summerlve/fa021540aabf887bd241.js"></script>
|
23
haozhang OP 我受不了了....你看下面的代码吧,上面贴的代码格式太差了。
|
24
haozhang OP |
25
otakustay 2015-07-27 11:52:27 +08:00
没错,race就是这么搞的,.map可以改成.each,更简单的写法:
Promise.race = (array) => new Promise((resolve, reject) => array.forEach((promise) => promise.then(resolve, reject))); all要更麻烦些,有参数收集等 |
26
haozhang OP |
27
ysmood 2015-07-31 06:21:23 +08:00
@otakustay race 和 all 的实现都不完全正确,或者不符合 ES6 的规范。数组的每个 item 可以不是 promise 的,应该用 resolve wrap 起来,比如 race 的例子:
Promise.race = (arr) => new Promise((resolve, reject) => arr.forEach((v) => Promise.resolve(v).then(resolve, reject))); 另外不要忘记判断 arr 是否是 iterable 类型。虽然有点吹毛求疵了 XD |