面试的时候常常会问到Promise的使用;有的面试官再深刻一点,会继续问是否了解Promise的实现方式,或者有没有阅读过Promise的源码;今天咱们就来看一下,Promise在内部是如何实现来链式调用的。javascript
所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。从语法上说,Promise 是一个对象,从它能够获取异步操做的消息。Promise
提供统一的API
,各类异步操做均可以用一样的方法进行处理。html
Promise出现以前都是经过回调函数来实现,回调函数自己没有问题,可是嵌套层级过深,很容易掉进回调地狱
。前端
const fs = require('fs');
fs.readFile('1.txt', (err,data) => {
fs.readFile('2.txt', (err,data) => {
fs.readFile('3.txt', (err,data) => {
//可能还有后续代码
});
});
});
复制代码
若是每次读取文件后还要进行逻辑的判断或者异常的处理,那么整个回调函数就会很是复杂且难以维护。Promise的出现正是为了解决这个痛点,咱们能够把上面的回调嵌套用Promise改写一下:java
const readFile = function(fileName){
return new Promise((resolve, reject)=>{
fs.readFile(fileName, (err, data)=>{
if(err){
reject(err)
} else {
resolve(data)
}
})
})
}
readFile('1.txt')
.then(data => {
return readFile('2.txt');
}).then(data => {
return readFile('3.txt');
}).then(data => {
//...
});
复制代码
promise最先是在commonjs社区提出来的,当时提出了不少规范。比较接受的是promise/A规范。可是promise/A规范比较简单,后来人们在这个基础上,提出了promise/A+规范,也就是实际上的业内推行的规范;es6也是采用的这种规范,可是es6在此规范上还加入了Promise.all、Promise.race、Promise.catch、Promise.resolve、Promise.reject等方法。es6
咱们能够经过脚原本测试咱们写的Promise是否符合promise/A+的规范。将咱们实现的Promise加入如下代码:面试
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
复制代码
而后经过module.exports导出,安装测试的脚本:npm
npm install -g promises-aplus-tests
复制代码
在实现Promise的目录执行如下命令:数组
promises-aplus-tests promise.js
复制代码
接下来,脚本会对照着promise/A+的规范,对咱们的脚原本一条一条地进行测试。更多规范可查看规范promise
咱们先回顾一下,咱们平时都是怎么使用Promise的:异步
var p = new Promise(function(resolve, reject){
console.log('执行')
setTimeout(function(){
resolve(2)
}, 1000)
})
p.then(function(res){
console.log('suc',res)
},function(err){
console.log('err',err)
})
复制代码
首先看出来,Promise是经过构造函数实例化一个对象,而后经过实例对象上的then方法,来处理异步返回的结果。同时,promise/A+规范规定了:
promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;
一个 Promise 的当前状态必须为如下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
var _this = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败缘由
function resolve(value) {}
function reject(reason) {}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
};
module.exports = Promise;
复制代码
当咱们实例化Promise时,构造函数会立刻调用传入的执行函数executor,咱们能够试一下:
let p = new Promise((resolve, reject) => {
console.log('执行了');
});
复制代码
所以在Promise中构造函数立马执行,同时将resolve函数和reject函数做为参数传入:
function Promise(executor) {
var _this = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败缘由
function resolve(value) {}
function reject(reason) {}
executor(resolve, reject)
}
复制代码
可是executor也会可能存在异常,所以经过try/catch来捕获一下异常状况:
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
复制代码
promise/A+规范中规定,当Promise对象已经由等待态(Pending)改变为执行态(Fulfilled)或者拒绝态(Rejected)后,就不能再次更改状态,且终值也不可改变。
所以咱们在回调函数resolve和reject中判断,只能是pending状态的时候才能更改状态:
function resolve(value) {
if(_this.state === PENDING){
_this.state = FULFILLED
_this.value = value
}
}
function reject(reason) {
if(_this.state === PENDING){
_this.state = REJECTED
_this.reason = reason
}
}
复制代码
咱们更改状态的同时,将回调函数中成功的结果或者失败的缘由都保存在对应的属性中,方便之后来获取。
当Promise的状态改变以后,无论成功仍是失败,都会触发then回调函数。所以,then的实现也很简单,就是根据状态的不一样,来调用不一样处理终值的函数。
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
};
复制代码
在规范中也说了,onFulfilled和onRejected是可选的,所以咱们对两个值进行一下类型的判断:
onFulfilled 和 onRejected 都是可选参数。若是 onFulfilled 不是函数,其必须被忽略。若是 onRejected 不是函数,其必须被忽略
代码写到这里,貌似该有的实现方式都有了,咱们来写个demo测试一下:
var myP = new Promise(function(resolve, reject){
console.log('执行')
setTimeout(function(){
reject(3)
}, 1000)
});
myP.then(function(res){
console.log(res)
},function(err){
console.log(err)
});
复制代码
然鹅,很遗憾,运行起来咱们发现只打印了构造函数中的执行
,下面的then函数根本都没有执行。咱们整理一下代码的运行流畅:
当then里面函数运行时,resolve因为是异步执行的,尚未来得及修改state,此时仍是PENDING状态;所以咱们须要对异步的状况作一下处理。
那么如何让咱们的Promise来支持异步呢?咱们能够参考发布订阅模式,在执行then方法的时候,若是当前仍是PENDING状态,就把回调函数寄存到一个数组中,当状态发生改变时,去数组中取出回调函数;所以咱们先在Promise中定义一下变量:
function Promise(executor) {
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
}
复制代码
这样,当then执行时,若是仍是PENDING状态,咱们不是立刻去执行回调函数,而是将其存储起来:
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
if(this.state === PENDING){
typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
typeof onRejected === 'function' && this.onRejected.push(onRejected)
}
};
复制代码
存储起来后,当resolve或者reject异步执行的时候就能够来调用了:
function resolve(value) {
if(_this.state === PENDING){
_this.state = FULFILLED
_this.value = value
_this.onFulfilled.forEach(fn => fn(value))
}
}
function reject(reason) {
if(_this.state === PENDING){
_this.state = REJECTED
_this.reason = reason
_this.onRejected.forEach(fn => fn(reason))
}
}
复制代码
有童鞋可能会提出疑问了,为何这边onFulfilled和onRejected要存在数组中,直接用一个变量接收不是也能够么?下面看一个例子:
var p = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 0)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
复制代码
咱们分别调用了两次then,若是是一个变量的话,最后确定只会运行后一个then,把以前的覆盖了,若是是数组的话,两个then都能正常运行。
至此,咱们运行demo,就能如愿以偿的看到运行结果了;一个四十行左右的简单Promise垫片就此完成了。这里贴一下完整的代码:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
var _this = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败缘由
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
function resolve(value) {
if(_this.state === PENDING){
_this.state = FULFILLED
_this.value = value
_this.onFulfilled.forEach(fn => fn(value))
}
}
function reject(reason) {
if(_this.state === PENDING){
_this.state = REJECTED
_this.reason = reason
_this.onRejected.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
if(this.state === PENDING){
typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
typeof onRejected === 'function' && this.onRejected.push(onRejected)
}
};
复制代码
相信上面的Promise垫片应该很容易理解,下面链式调用才是Promise的难点和核心点;咱们对照promise/A+规范,一步一步地来实现,咱们先来看一下规范是如何来定义的:
then 方法必须返回一个 promise 对象
promise2 = promise1.then(onFulfilled, onRejected);
也就是说,每一个then方法都要返回一个新的Promise对象,这样咱们的then方法才能不断的链式调用;所以上面的简单垫片中then方法就不适用了,由于它什么都没有返回,咱们对其进行简单的改写,不论then进行什么操做,都返回一个新的Promise对象:
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject)=>{
})
return promise2
}
复制代码
咱们继续看then的执行过程:
[[Resolve]](promise2, x)
首先第一点,咱们知道onFulfilled和onRejected执行以后都会有一个返回值x,对返回值x处理就须要用到Promise解决过程,这个咱们下面再说;第二点须要对onFulfilled和onRejected进行异常处理,没什么好说的;第三和第四点,说的实际上是一个问题,若是onFulfilled和onRejected两个参数没有传,则继续往下传(值的传递特性);举个例子:
var p = new Promise(function(resolve, reject){
setTimeout(function(){
resolve(3)
}, 1000)
});
p.then(1,1)
.then('','')
.then()
.then(function(res){
//3
console.log(res)
})
复制代码
这里无论onFulfilled和onRejected传什么值,只要不是函数,就继续向下传入,直到有函数进行接收;所以咱们对then方法进行以下完善:
//_this是promise1的实例对象
var _this = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
var promise2 = new Promise((resolve, reject)=>{
if(_this.state === FULFILLED){
let x = onFulfilled(_this.value)
resolvePromise(promise2, x, resolve, reject)
} else if(_this.state === REJECTED){
let x = onRejected(_this.reason)
resolvePromise(promise2, x ,resolve, reject)
} else if(_this.state === PENDING){
_this.onFulfilled.push(()=>{
let x = onFulfilled(_this.value)
resolvePromise(promise2, x, resolve, reject)
})
_this.onRejected.push(()=>{
let x = onRejected(_this.reason)
resolvePromise(promise2, x ,resolve, reject)
})
}
})
复制代码
咱们发现函数中有一个resolvePromise,就是上面说的Promise解决过程,它是对新的promise2和上一个执行结果 x 的处理,因为具备复用性,咱们把它抽成一个单独的函数,这也是上面规范中定义的第一点。
因为then的回调是异步执行的,所以咱们须要把onFulfilled和onRejected执行放到异步中去执行,同时作一下错误的处理:
//其余代码略
if(_this.state === FULFILLED){
setTimeout(()=>{
try {
let x = onFulfilled(_this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(_this.state === REJECTED){
setTimeout(()=>{
try {
let x = onRejected(_this.reason)
resolvePromise(promise2, x ,resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(_this.state === PENDING){
_this.onFulfilled.push(()=>{
setTimeout(()=>{
try {
let x = onFulfilled(_this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
_this.onRejected.push(()=>{
setTimeout(()=>{
try {
let x = onRejected(_this.reason)
resolvePromise(promise2, x ,resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
复制代码
Promise 解决过程是一个抽象的操做,其需输入一个 promise 和一个值,咱们表示为
[[Resolve]](promise, x)
,若是 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;不然其用 x 的值来执行 promise 。
这段话比较抽象,通俗一点的来讲就是promise的解决过程须要传入一个新的promise和一个值x,若是传入的x是一个thenable的对象(具备then方法),就接受x的状态:
//promise2:新的Promise对象
//x:上一个then的返回值
//resolve:promise2的resolve
//reject:promise2的reject
function resolvePromise(promise2, x, resolve, reject) {
}
复制代码
定义好函数后,来看具体的操做说明:
首先第一点,若是x和promise相等,这是一种什么状况呢,就是至关于把本身返回出去了:
var p = new Promise(function(resolve, reject){
resolve(3)
});
//Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
var p2 = p.then(function(){
return p2
})
复制代码
这样会陷入一个死循环中,所以咱们首先要把这种状况给排除掉:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle'));
}
}
复制代码
接下来就是对不一样状况的判断了,首先咱们把 x 为对象或者函数的状况给判断出来:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle'));
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//函数或对象
} else {
//普通值
resolve(x)
}
}
复制代码
若是 x 为对象或函数,就把 x.then 赋值给 then好理解,可是第二点取then有可能会报错是为何呢?这是由于须要考虑到全部出错的状况(防小人不防君子),若是有人实现Promise对象的时候使用Object.defineProperty()恶意抛错,致使程序崩溃,就像这样:
var Promise = {};
Object.defineProperty(Promise, 'then', {
get: function(){
throw Error('error')
}
})
//Uncaught Error: error
Promise.then
复制代码
所以,咱们取then的时候也须要try/catch:
//其余代码略
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//函数或对象
try {
let then = x.then
} catch(e){
reject(e)
}
}
复制代码
取出then后,回到3.3,判断若是是一个函数,就将 x 做为函数的做用域 this 调用,同时传入两个回调函数做为参数。
//其余代码略
try {
let then = x.then
if(typeof then === 'function'){
then.call(x, (y)=>{
resolve(y)
}, (r) =>{
reject(r)
})
} else {
resolve(x)
}
} catch(e){
reject(e)
}
复制代码
这样,咱们的链式调用就能顺利的调用起来了;可是还有一种特殊的状况,若是resolve的y值仍是一个Promise对象,这时就应该继续执行,好比下面的例子:
var p1 = new Promise((resolve, reject)=>{
resolve('p1')
})
p1.then((res)=>{
return new Promise((resolve, reject)=>{
resolve(new Promise((resolve, reject)=>{
resolve('p2')
}))
})
})
.then((res1)=>{
//Promise {state: "fulfilled", value: "p2"}
console.log(res1)
})
复制代码
这时候第二个then打印出来的是一个promise对象;咱们应该继续递归调用resolvePromise(参考规范3.3.1),所以,最终resolvePromise的完整代码以下:
function resolvePromise(promise2, x, resolve, reject){
if(promise2 === x){
reject(new TypeError('Chaining cycle'))
}
if(x && typeof x === 'object' || typeof x === 'function'){
let used;
try {
let then = x.then
if(typeof then === 'function'){
then.call(x, (y)=>{
if (used) return;
used = true
resolvePromise(promise2, y, resolve, reject)
}, (r) =>{
if (used) return;
used = true
reject(r)
})
} else {
if (used) return;
used = true
resolve(x)
}
} catch(e){
if (used) return;
used = true
reject(e)
}
} else {
resolve(x)
}
}
复制代码
到这里,咱们的Promise也可以完整的实现链式调用了;而后把代码用promises-aplus-tests测试一下,完美的经过了872项测试。
promise/A规范(英文) promise/A+规范(英文) promise/A+规范(中文)
更多前端资料请关注公众号【前端壹读】
。