结合Promises/A+规范,深入解读Promise(附源码)

1,103 阅读6分钟

目录


初步了解Promise

所谓Promise,字面上可以理解为“承诺”,表示一次操作的结果。和一般操作不同的是,Promise 对象不是实时的,而是在未来的某一时刻,会有返回,是一个能代表未来出现的结果的promise对象。 Promise允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。

我们知道,JavaScript 在浏览器中是单线程调用的,但是有时我们又需要异步操作,例如访问文件,或者是调用 ajax。这时候,我们就需要 Promise 了。

一个Promise有三种状态:

  • pending:初始(等待)态,既不是成功,也不是失败
  • fulfilled(resolved):意味着操作成功完成
  • rejected:意味着操作失败

一个Promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

Promise实现的类库

为什么需要这些类库?

为什么需要这些类库呢?我想有些读者不免会有此疑问。首先能想到的原因是有些运行环境并不支持 ES6 Promises 。

Promise兼容如下

image

Promise扩展类库

Promise扩展类库除了实现了Promise中定义的规范之外,还增加了自己独自定义的功能。

Promise扩展类库数量非常的多,到底选择哪个来使用完全看自己的喜好了,下面我们只提到其中两个比较有名的

petkaantonov/bluebird

这个类库除了兼容 Promise 规范之外,还扩展了取消promise对象的运行,取得promise的运行进度,以及错误处理的扩展检测等非常丰富的功能,此外它在实现上还在性能问题下了很大的功夫。

Bluebird 提供了很多方法来帮助我们把现有的库函数由 Callback 的方式,转化为 Promise 的方式,这叫做 Promise 化。最常见的,例如 Node 中默认提供的 fs 文件操作句柄,我们可以通过 Promise.promiseifyAll(fs) 的方式,把 fs 的调用,通过 Promise 返回。

let Promise = require("bluebird");
let fs = require("fs");
Promise.promisifyAll(fs);//这里 fs 被改写了,增加了 Async 后缀的方法
fs.readFileAsync('./welcome.txt', 'utf8').then((data) =>{
    console.log(data);
}
kriskowal/q类库

Q 实现了 Promises 和 Deferreds 等规范。 它自2009年开始开发,还提供了面向Node.js的文件IO API Q-IO 等, 是一个在很多场景下都能用得到的类库。

深度解析Promise + 源码实现

new Promise( function(resolve, reject) {...} );

参数

在new Promise 时,需要传递一个executor 执行器,执行器会立刻执行。执行器中传递两个参数, resolve reject

  • resolve:异步操作执行成功后的回调函数
  • reject:异步操作执行失败后的回调函数

源码:new Promise源码实现,github查看代码 点击查看源码


Promise 原型方法

then
Promise.prototype.then(onFulfilled, onRejected)

onFulfilled

当Promise成功态(fulfillment)时,该参数作为回调函数被调用。该函数有一个参数,即成功时返回的最终结果(value)

onRejected

当Promise失败态(rejection )时,该参数作为回调函数被调用。该函数有一个参数,即拒绝的原因(reason)。

详细解读
  • 每个Promise都有then方法(可以说,then就是promise的核心),而且then必须返回一个promise
  • 同一个Promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
  • then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用
  • 如果then中返回的是一个结果的话,会把这个结果传递给下一个then
  • 如果then返回的是一个promise的话,会等待这个promise执行完,决定返回的那个 promise是成功还是失败,如果成功走下一个then的成功
  • 每个then都返回一个新的promise,为什么返回一个新的promise而不是this,因为 promise状态确定好,就不能更改

then是否返回一个新的Promse,测试代码如下:

let promise1 = new Promise(function(resolve){
    resolve(1);
});
let thenPromise = promise1.then(function(value){
    console.log(value);
});
let catchPromise = thenPromise.catch(function(error){
    console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true

如上代码,打印的都是true,这说明不管是then还是catch都返回的和新创建的promise是不同的对象


源码:上面关于then的详细解读已经描述的很详细了,实现代码见github 点击查看源码


catch
Promise.prototype.catch(onRejected)

onRejected

当Promise 被rejected时,被调用的一个Function。 该函数拥有一个参数:reason 即rejection 失败时返回的原因。

详细解读
  • 和then一样返回结果为新的promise
  • 捕获then没有捕获到的异常,一般放在最后,放在中间没有意义
let p1 = Promise.resolve('成功')
p1.then(() => {
  console.log('p1 then')
  throw new Error();
}).catch(err => {
  console.log('catch ' + err);
})
//结果:
// p1 then
// catch Error

注:即使你坚信不会出现异常,添加一个 catch() 总归是更加谨慎的。如果你的假设最终被发现是错误的,它会让你的生活更加美好。

方法

1. resolve

返回一个状态为成功的Promise对象,并将给定数据结果传递给对应的处理方法

Promise.resolve(value)
2. reject

返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

Promise.resolve(reason)
3. all

参数为数组,返回数组中所有的请求结果,并且返回数据的顺序与请求的数组顺序一致。返回时间以最慢的为准(要么全部成功,要么全部失败)

// 要么全部成功
let p = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.all(p).then(res => {
  console.log(res) // [1, 2]
})

// 要么就失败
let p1 = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => reject(2))
]
Promise.all(p1).then(res => {
  console.log(res)
},err => {
  console.log(err); // 2
})

源码:Promise.all()源码实现,可直接下载测试上面例子 点击查看源码


4. race

参数为数组,返回的结果以最快的为准,且只有一个结果(谁最快就返回谁的结果)

// 要么全部成功,返回执行完最快的结果
let p = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.race(p).then(res => {
  console.log('success ' + res) // success 1或success 2 哪个执行完成的快返回哪个结果
})

// 要么就失败
let p1 = [
  new Promise((resolve, reject) => reject(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.race(p1).then(res => {
  console.log('success' + res)
},err => {
  console.log('err'); // err
})

源码:Promise.race()源码实现,可直接下载测试上面例子 源码实现见gitHub


以上详细解读,能帮助您更加了解Promise,下面一个经典的图,帮助大家更好的理解promise

image

完整源码

为了方便大家测完整代码,详见gitHub

总结

异步编程,它帮助我们避免同步代码带来的线程阻塞的问题,过多的回调嵌套很容易会让我们陷入“回调地狱”中,使代码变成一团乱麻。使用Promise,可以有效的解决这个问题,并且还可以使我们的代码更加清晰、易读。

参考阅读:
作者:香香

将来的你,一定会感谢现在拼命努力的自己!