6

前言

如果你看了很多的Promise的文档和资源,但是却发现自己总是掌握不了Promise的精髓,也许你可以看看我的的这篇文章,或许对你有所帮助。

JS的运行机制

在学习使用Promise之前,需要对JS的运行机制有所了解。

JavaScript的并发模型基于"事件循环",这个模型与像 C 或者 Java 这种其它语言中的模型截然不同。

可视化描述

clipboard.png

函数调用形成了一个栈帧。

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的运行机制。

clipboard.png

前面已经给大家介绍了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调用链


火星田园犬
933 声望685 粉丝

小心驾驶, 专业埋雷