深刻学习Promise

前言

若是你看了不少的Promise的文档和资源,可是却发现本身老是掌握不了Promise的精髓,也许你能够看看个人的这篇文章,或许对你有所帮助。git

JS的运行机制

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

JavaScript的并发模型基于"事件循环",这个模型与像 C 或者 Java 这种其它语言中的模型大相径庭。github

可视化描述

clipboard.png

函数调用造成了一个栈帧。segmentfault

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返回的时候,栈就空了。promise

对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。并发

队列

一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都与一个函数相关联。当栈拥有足够内存时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及于是建立了一个初始堆栈帧)。当栈再次为空的时候,也就意味着消息处理结束。异步

事件循环

之因此称为事件循环,是由于它常常被用于相似以下的方式来实现:函数

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

若是当前没有任何消息queue.waitForMessage 会等待同步消息到达。 oop

参考资料: 并发模型与事件循环学习

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调用链