promise相关

1.Promise

一个Promise具有三个状态,分别是pending,fulfilled,rejected;分别代表着初始状态;操作成功完成;操作失败;pending状态的promise可以转化为fulfilled状态,此时onfulfilled方法将会被触发执行;当pending状态转化为rejected状态的话,那么此时onrejected方法将会被触发。一个promise的resolve()和reject()都会产生状态,分别是fulfilled和rejected。如果这个promise没有定义onfulfilled或者onrejected的话,那么回调将不会被调用;一般onfulfilled都是作为Promise.prototype.then方法的第一个cb参数的,而onrejected都是作为Promise.prototype.then的第二个cb参数;如果在then中没有传递onrejected的话,那么可以使用Promise.prototype.catch来对错误操作进行兜底操作。

1
2
3
4
5
6
// 输出 2
new Promise((resolve, reject) => {
reject(2)
}).then(() => {}, err => {
console.error(err);
})

Promise.prototype.then和Promise.prototype.catch方法将会返回一个Promise,如果其不是显式的Promise数据类型的话,那么是会经过类型转换的,所以可以实现链式调用。可以看下面这个例子:

1
2
3
4
5
6
7
// 输出 number is 2
new Promise((resolve, reject) => {
reject(2);
}).catch(err => err)
.then(n => {
console.log('number is', n);
})

2.对promise运行情况的判断

1.promise哪部分是立执行的,哪部分是异步的

1
2
3
4
5
6
7
8
// 输出1 2 3
new Promise((resolve, reject) => {
console.log(1);
resolve();
}).then(() => {
console.log(3);
})
console.log(2);

2.重点就是区别出那部分是未来执行的,以及先进入排队的未来操作会被先执行:

1
2
3
4
5
6
7
// 输出 1 2 3 4
new Promise((resolve, reject) => {
console.log(1);
resolve();
new Promise(resolve => {resolve();}).then(() => console.log(3))
}).then(() => {console.log(4);})
console.log(2);

3.奇怪现象之当then接收到不是function的时候

1
2
3
4
5
6
// 输出 1 2 3
new Promise((resolve, reject) => {
console.log(1);
resolve();
}).then(console.log(2));
console.log(3);

在MDN上的描述为,如果then上面的不是一个function的话,那么将会使会利用then里面的东西创建一个新的Promise。

3.分析一个Promise的大致运行流程

代码如下所示(调试工具使用Chrome,Promise实现采用lie库)

1
2
3
4
5
6
new Promise((resolve, reject) => {  // 再此处打一个端点
console.log(1);
resolve(3);
}).then(data => {
console.log(data);
})

运行流程分析:

1.首先进入到Promise构造方法里面,判断构造函数所接收到的参数是否是一个function(使用typeof进行判断),如果不是的话那么报错。

1
2
3
if (typeof resolver !== 'function') {           // resolve 是Promise构造函数的形式化参数
throw new TypeError('resolver must be a function');
}

2.每个Promise实例都只能有三种状态当中的一个,要么pending,要么是fulfilled,或者是rejected。很显然初始状态自然是pending;

1
2
// In Promise constructor
this.state = ['PENDING']; // 是一个数组,为什么得是数组后面揭晓

3.设置未来执行的操作队列:

1
2
// In Promise constructor
this.queue = [];

4.设置outcome值,可以理解为这个值会被用来传给onfulfilled回调和onrejected回调:

1
2
// In Promise Constructor
this.outcome = void 0;

5.准备工作已经就绪,接下来开始执行Promise的立执行部分,立执行部分就是传递给Promise构造函数的参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
safelyResolveThenable(this, resolver);    // 其中this执行Promise实例,resolver就是传递给Promise构造函数的函数参数


// 下面是safelyResolveThenable函数的定义:
function safelyResolveThenable(self, thenable) {
// 对于一个Promise实例来说,最终的结果要么是resolve,要么就是reject。且状态只能是发生一次,所以有了下面的called
var called = false;
function onError(value) {
if (called) {
return;
}
called = true;
handlers.reject(self, value);
}

function onSuccess(value) {
if (called) {
return;
}
called = true;
handlers.resolve(self, value);
}

function tryToUnwrap() {
thenable(onSuccess, onError);
}

var result = tryCatch(tryToUnwrap);
if (result.status === 'error') {
onError(result.value);
}
}

// 还有一个很重要的handlers全局对象的定义,如下所示:
var handlers = {};
handlers.resolve = function (self, value) {
var result = tryCatch(getThen, value);
if (result.status === 'error') {
return handlers.reject(self, result.value);
}
var thenable = result.value;

if (thenable) {
safelyResolveThenable(self, thenable);
} else {
self.state = FULFILLED; // FULFILLED 的值是一个数组["fulfilled"]
self.outcome = value;
var i = -1;
var len = self.queue.length;
while (++i < len) {
self.queue[i].callFulfilled(value);
}
}
return self;
};
handlers.reject = function (self, error) {
self.state = REJECTED; // REJECTED 的值是一个数组["rejected"]
self.outcome = error;
var i = -1;
var len = self.queue.length;
while (++i < len) {
self.queue[i].callRejected(error);
}
return self;
};

6.运行safelyResolveThenable函数,首先设置called对象,因为一个Promise实例也就只能被决议一次,要么是fulfilled,要么是rejected。接下来定义了onError和onSuccess,分别对应构造Promise对象时候的function argument的第一个argument和第二个argument,然后执行tryCatch方法。这个tryCatch方法接受一个函数,这个函数完成的工作就是:执行构造Promise对象时候传递的函数参数。所以说,Promise构造函数所接受的resolver参数是立执行的。

7.运行tryCatch方法,tryCatch的函数签名如下所示:

1
2
3
4
5
6
7
8
9
10
11
function tryCatch(func, value) {
var out = {};
try {
out.value = func(value);
out.status = 'success';
} catch (e) {
out.status = 'error';
out.value = e;
}
return out;
}

第一次执行的时候,tryCatch接受的参数是实例化Promise时所传入的resolver,因此如果执行resolver的时候如果报错的话,那么状态将会是’error’,而非必须是reject所导致。(这一点挺重要,就是状态为’error’)。如果状态为’error’的话,那么在调用reject,也就是说间接的reject了,这就造成了下面这样的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 输出 error is Error: error...
new Promise((resolve, reject) => {
throw new Error('error');
}).then(() => {}, err => {
console.log('error is', err);
})

// 同时需要区别开下面这种情况:
new Promise((resolve, reject) => {
resolve(new Error('err'));
}).then(err => {
console.log('resolve', err); // 此时promise的状态可不是rejected了,而是正常的fulfilled
})

8.对于我们debug的这个例子来说,运行到这步的时候就已经输出1了,同时resolve被替代为handlers.resolve方法,所以接下来执行handlers.resolve方法。回到上面的handlers.resolve方法,这个方法第一步也是调用的tryCatch,关键是要留意这个tryCatch的第一个参数不再是之前的resolver了,而是getThen方法,getThen的函数定义如下所示:

1
2
3
4
5
6
7
8
9
function getThen(obj) {
// Make sure we only access the accessor once as required by the spec
var then = obj && obj.then;
if (obj && (typeof obj === 'object' || typeof obj === 'function') && typeof then === 'function') {
return function appyThen() {
then.apply(obj, arguments);
};
}
}

它的作用就是判断resolve的参数是否又是一个Promise,如果是的话,那么传递给onfulfill或者onrejected的参数可就不是这个Promise instance value了,而是这个Promise instance 决议后的value。可以看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 输出为 1,2几乎同时输出,但是3滞后2S输出,同时值为n为3
new Promise((resolve, reject) => {
console.log('1', new Date);

setTimeout(function() {
resolve(new Promise((resolve, reject) => {

setTimeout(function() {
resolve(3);
}, 1000);

}));
}, 1000);
}).then(n => {
console.log('3', new Date, n);
});
console.log('2',new Date);

哪怕是resolve多个Promise,最外层的onfulfilled或者onrejected也是获取最里层的promise所返回的值。如下所示是resolve 两个promise。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 步骤1和步骤2几乎同时输出;步骤3延时2S,输出4
new Promise((resolve, reject) => {
console.log('1', new Date);

setTimeout(function() {
resolve(new Promise((resolve, reject) => {

setTimeout(function() {
resolve(3);
}, 1000);

}).then(n => n + 1))
}, 1000);
}).then(n => {
console.log('3', n, new Date);
})
console.log('2', new Date);

为什么能够处理多个Promise?这就需要归功于handlers.resolve的实现了,在里面会判断tryCatch所返回的对象中的value值是否存在,如果存在的话,那么表明又是一个实例化Promise的过程,此时又会走一遍上面走过的流程。因此此时仍旧处于未决议的状态,所以onfulfilled也好onrejected也好都得不到执行的机会。

9.执行到这步的时候,只要resolve后面跟的是非Promise对象的话,那么此时都是已经决议完成了,决议完成的话,将会执行下面这些步骤:

1
2
3
4
5
6
7
8
9
10
11
12
if (thenable) {
safelyResolveThenable(self, thenable);
} else {
self.state = FULFILLED;
self.outcome = value;
var i = -1;
var len = self.queue.length;
while (++i < len) {
self.queue[i].callFulfilled(value);
}
}
return self;

首先设置了决议状态位fulfilled,接着还设置了传递给onfulfilled函数的值outcome,到了这一步Promise的立执行部分就已经基本运行完了。因此这个时候开始走then流程了,then流程的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.prototype.then = function (onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
typeof onRejected !== 'function' && this.state === REJECTED) {
return this;
}
var promise = new this.constructor(INTERNAL);
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
unwrap(promise, resolver, this.outcome);
} else {
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}

return promise;
};

从源代码我们可以看出其实then语句部分也是立执行的(意思是说只要一旦Promise决议好了的话,那么便开始执行then部分的代码了)。此时会根据决议状态确定是执行onfulfilled还是onrejected。从上面我们可以看出,onfulfilled以及onrejected并没有要求一定得是函数类型的,而这也正是上面一个例子第三个例子onfulfill表现出来像是同步执行的原因。

10.执行unwrap方法流程,unwrap方法的函数定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function unwrap(promise, func, value) {
immediate(function () {
var returnValue;
try {
returnValue = func(value);
} catch (e) {
return handlers.reject(promise, e);
}
if (returnValue === promise) { // 万一你皮了返回自己那岂不是then到无求无尽了嘛
handlers.reject(promise, new TypeError('Cannot resolve promise with itself'));
} else {
handlers.resolve(promise, returnValue);
}
});
}

11.执行immediate方法,immediate方法的定义如下所示:

1
2
3
4
5
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}

12.接下来执行scheduleDrain方法,他的函数定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var Mutation = global.MutationObserver || global.WebKitMutationObserver;

var scheduleDrain;

{
if (Mutation) {
var called = 0;
var observer = new Mutation(nextTick);
var element = global.document.createTextNode('');
observer.observe(element, {
characterData: true
});
scheduleDrain = function () { // 每次调用scheduleDrain都会改变element,而element是被监视了的,所以此时nextTick会被触发,属于微观层面的异步行为
element.data = (called = ++called % 2);
};
} else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
var channel = new global.MessageChannel();
channel.port1.onmessage = nextTick;
scheduleDrain = function () {
channel.port2.postMessage(0);
};
} else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
scheduleDrain = function () {

// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement('script');
scriptEl.onreadystatechange = function () {
nextTick();
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
};
} else {
scheduleDrain = function () {
setTimeout(nextTick, 0);
};
}
}

上面这部分代码就是Promise的核心精华部分之一,也正是在这一步决定了onfulfill和onreject是被异步调用的原因之一,同时他们是属于微观层面的异步事件,会比setTimeout等的宏观层面的异步时间更早得到执行机会。那么是大致怎么实现的呢?通过阅读源代码可以发现依赖于scheduleDrain这个方法的实现。

首先需要明确的是,在大部分场合下,scheduleDrain的实现都不是依赖于setTimeout这种宏观层面的异步流程来实现的。除非是最最不济的情况下才会使用到setTimeout。所以再这里就需要了解到如何实现一个微观层面上的异步流程了,分析scheduleDrain函数的实现,我们可以发现它首先使用DOM3 Events所提供的window.MutationObserver,这个接口提供了监视DOM树发生变化时所作出更改的能力。我们可以看一下它的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let config = {
attributes: true,
childList: true,
subtree: true
};

let cb = () => {
console.log('changed');
};

let observer = new MutationObserver(cb); // 当被监听的DOM发生变化的时候便会执行这个cb

observer.observe($0, config);
$0.style.width = '20px'; // 这个时候你会发现changed被输出

关于node上面的处理,这里就不讨论了。接下来继续看关于使用script标签来实现微观层面的异步行为。使用script来实现异步行为的注释写的很清楚,只要将script挂载上去,那么onreadystatechange便会被异步触发(微观层面)。唯一需要注意的就是需要清空资源。如果这些方式都实现不了的话,那么就使用setTimeout(nextTick, 0)代替。

为什么需要明确说明微观层面的异步行为呢?因为微观层面的异步行为比如Promise,宏观层面的异步行为如setTimeout,需要明确的是微观层面的异步行为会比宏观层面的异步行为更快的执行。

分析到这里,此时已经基本完毕了。有一点需要注意的是由于then放回的十一Promise,所以还会走一遍上面分析的流程。

微观层面和宏观层面的异步行为分析

1
2
3
4
5
6
7
8
9
10
// 输出结果为1,2,3
setTimeout(function() {
console.log('3', new Date);
}, 0);
new Promise((resolve, reject) => {
resolve(1);
}).then(n => {
console.log('2', new Date);
});
console.log('1', new Date);