前言
如果你看了很多的Promise的文档和资源,但是却发现自己总是掌握不了Promise的精髓,也许你可以看看我的的这篇文章,或许对你有所帮助。
JS的运行机制
在学习使用Promise之前,需要对JS的运行机制有所了解。
JavaScript的并发模型基于"事件循环",这个模型与像 C 或者 Java 这种其它语言中的模型截然不同。
可视化描述
栈
函数调用形成了一个栈帧。
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7));
当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量。当bar调用foo时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了foo的参数和局部变量。当foo返回时,最上层的帧就被弹出栈(剩下bar函数的调用帧 )。当bar返回的时候,栈就空了。
堆
对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。
队列
一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈拥有足够内存时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因而创建了一个初始堆栈帧)。当栈再次为空的时候,也就意味着消息处理结束。
事件循环
之所以称为事件循环,是因为它经常被用于类似如下的方式来实现:
while (queue.waitForMessage()) {
queue.processNextMessage();
}
如果当前没有任何消息queue.waitForMessage 会等待同步消息到达。
参考资料: 并发模型与事件循环
Promise回顾
Promise其实是一个函数,它的实例是一个不可逆的状态机。通常可以使用下面这几种方式去创建一个Promise的实例。
使用 new
const promise = new Promise((resolve, reject) => {
......
resolve(xxx)
......
});
使用静态API
Promise.resolve
, Promise.reject
, Promise.all
, Promise.race
等提供的静态方法,执行完毕后,也会返回一个Promise。
const promise = Promise.resolve('complete');
// promise instanceof Promise => true
添加回调函数返回一个Promise
当我们在一个Promise的实例中,使用then, catch, finally添加完回调函数也会返回一个Promise。
const promise = Promise.resolve('complete').then((val)=> {
console.log(val) // complete
})
// promise instanceof Promise => true
Promise的实例状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
当Promise的实例状态由 pending => fulfilled, 会触发then中注册的第一个函数。
当Promise的实例状态由 pending => rejected, 会触发then中注册的第二函数或者catch中注册的函数。
当Promise的实例状态发生变化后,finally注册的函数都会触发。
Promise 运行机制
了解Promise的运行机制,能够帮助我们更好的使用Promise。下面这张图是根据 es6-promise 的实现而描绘的Promise的运行机制。
前面已经给大家介绍了JS的运行机制,JS是基于事件循环,当JS运行的消息队列被清空后,将会去异步队列中获取消息,加入JS的运行队列。 而在有了Promise以后, 将会在获取异步队列消息之前,会把Promise的运行队列全部添加到JS运行消息队列。在此运行期间, 如果Promise运行队列又添加了新的回调函数, Promise运行队列又会重新添加到JS运行的消息队列中, 直到Promise的运行队列和JS运行的消息队列都清空,才会去异步队列中获取消息。这样就能保证Promise的回调函数都在异步调用之前。但是开发时候也需要防止进入Promise的回调陷阱,就像下面这样
function bar() {
Promise.resolve('resolve').then((val) => {
console.log(val);
bar();
})
}
bar()
console.log('continue');
setTimeout(() => {console.log('setTimeout')});
// continue resolve resolve resolve resolve ......
上面的例子中setTimeout将永远不会被输出。
Promise实例的任何状态(pending, resolved, rejected)都能使用then, catch, finally来注册(添加)回调函数。而不同的是,在pending状态的Promise实例会把这些回调函数存储在内部,等到状态发生改变的时候,再把的相关回调函数全部推送promise的运行队列。而处在resolved与rejected状态的实例,将会立即把相关函数推送到Promise的运行队列,而不需要等待。
var callback;
var promise = new Promise((resolve) => {
callback = resolve;
})
promise.then(() => {console.log('1')});
Promise.resolve('2').then((val) => {console.log(val)});
callback();
// 输出 2 1
上面例子中的promise
将会在callback执行之后,才会把所注册的函数推入Promise的运行队列,所以导致所注册的函数运行在后面。
Promise的实例状态为resolved时。只会把then中resolve和finally所注册函数添加到Promise的运行队列。而且它们的执行顺序只与它们的添加顺序有关。
var promise = new Promise((resolve, reject) => {
resolve('ok');
})
promise.finally(() => {
console.log('finally');
});
promise.then((val) => {
console.log(val);
});
promise.catch(() => {
console.log('catch');
});
// finally ok
很明显 finally 出现在 ok 之前,catch所注册的函数也将不会被推送到Promise的运行队列,也将不会被执行。
Promise的实例状态为rejected的时。会把then中reject和catch与finally所注册函数添加到Promise的运行队列。而且它们的执行顺序只与它们的添加顺序有关。
var promise = Promise.reject('reject');
promise.finally(() => {console.log('finally')});
promise.catch(() => {console.log('catch')});
promise.then(() => {console.log('ok')}, (val) => {console.log(val)});
// finally catch reject
上面就是个人关于Promise的探究与学习,与此相辅的还有另外一篇,深入学习Promise调用链。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。