1.PROMISE的诞生
首先要明确的是PROMISE是一个类,而不是一个新的功能,它是对异步代码进行整合,解决异步层层嵌套的问题,让代码看上去更加的整洁,所以在es6中他需要用new关键字来进行实例。
promise之前的写法:
doSomething(successCallback,failCallback);
多个连续异步回调时
doSomething(function successCallback(){
doSecondSomething(function successCallback(){
...
},failCallback);
},failCallback);
promise的写法:
var promise=doSomething();
promise.then(successCallback,failCallback);
或者
doSomething().then(successCallback,failCallback);
多个连续异步回调时
doSomething().then(successCallback,failCallback).then(successCallback,failCallback)
对比可以发现:
- 多个异步操作嵌套,会导致回调地狱。
- 多个异步并行进行,执行的顺序是不可控的。
- 如果异步过多会导致代码臃肿,不易于阅读。
2.PROMISE的雏形
我们先从下面的一个实例中理解下PROMISE的重点内容。
let promise1 = new Promise((resolve, reject) => {
resolve('data')
})
promise1.then(data => {
console.log(data)
})
let promise2 = new Promise((resolve, reject) => {
reject('error')
})
promise2.then(data => {
console.log(data)
}, err => {
console.log(err)
})
在使用new关键字调用promise构造函数时,在合适的时机(往往是在异步操作结束时),调用executor的参数resolve,并将经过resolve处理后的值作为resolve的函数参数执行,这个值便可以在后续then方法的第一个函数参数(onfulfilled)中拿到;同理,再出现错误的时候调用reject,并将错误信息作为reject的函数参数执行,这个错误信息就可以在then方法的第二个参数(onrejected)中拿到。
因此,我们在实现primise的时候应该有两个变量,分别存储经过resolve处理过的值,以及经过reject处理过后的值,同事还需要存在一个状态,这个状态就是promise的实例状态(pending,fulfilled,rejected);最后要提供一下resolve和reject方法,这两个方法需要作为executor的参数提供给开发者.代码如下。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = value => {
this.value = value
}
const reject = reason=> {
this.reason = reason
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value)
onrejected(this.reason)
}
为了保证onfulfilled、onreject能够正常执行,给其设置了默认值,一个函数元(Function.prototype)。 因为resolve的最终调用是由开发者在不确定的环境下执行的(一般是在全局)。这里使用了箭头函数是为确保函数内部的this不会被搞混,如果没有使用箭头函数就需要用一个值去存储当前promise中的this。至于把then方法放在原型链上是因为每个Promise的实例then都是一样的,实例在调用该方式的时候可以通过原型直接去调用,不需要每次实例化都创建一个then方法,节省内存。
3.PROMISE的状态完善
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}
const reject = reason=> {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onfulfilled === 'function' ? onrejected : err => {throw err}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
}
resolve和reject方法加入了判断只允许从pending变成fulfilled。或者从pending变为rejected。并且对Promise.then.onfulfilled和onrejected进行了判断,当参数不是函数类型时,就赋予函数值。
4.PROMISE异步完善
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
})
})
promise.then(data => {
正常来说上面代码会在2s后输出data,但是犹豫目前代码只支持同步的操作,所以在执行then()方法的时候他的状态没有改变,所以并没有执行传过去的方法。所以我们需要在then方法时判断status状态为pending时把他传递过来的方法储存起来。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFunc(this.value)
}
}
const reject = reason=> {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.value)
}
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onfulfilled === 'function' ? onrejected : err => {throw err}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onfulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
但是上面的操作还是存在了一个问题
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
})
})
promise.then(data => {
console.log(data)
})
按理说上面的代码应该先输出1然后再输出data,因此我们需要把resolve和reject放到任务队列当中,我们先将其放入setTimeout当中,保证异步操作,这样不是很严谨,因为promise输出microtasks,所以promise的实现运用了MutationObserver来模仿nextTick。所以目前的代码如下
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFunc(this.value)
}
})
}
const reject = reason=> {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.value)
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onfulfilled === 'function' ? onrejected : err => {throw err}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onfulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
5.Promise细节完善
目前的promise已经比较靠谱了,但是我们还是需要完善一些细节,比如,在promise实例之前添加多个then()方法。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
},2000)
})
promise.then(data => {
console.log('1:' + data)
})
promise.then(data => {
console.log('2:', data)
})
正常情况上面的代码会输出
1:data
2:data
但是事实上只会输出2:data因为第二个方法的onfulfilledFunc会覆盖第一个then房中的onfulfilledFunc,解决这个问题我们只需要将所有的then方法储存到一个数组里面,在当前的Promise被决议的时候执行数组里面的方法。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFuncArr = []
this.onRejectedFuncArr = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFuncArr.forEach(func => {
func(this.value)
})
}
})
}
const reject = reason=> {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFuncArr.forEach(func => {
func(this.value)
})
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onfulfilled === 'function' ? onrejected : err => {throw err}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onfulfilledFuncArr.push(onfulfilled)
this.onRejectedFuncArr.push(onrejected)
}
}
另外一个细节是,如果在构造函数中出错,就会触发Promise的rejected方法,所以下面我用了try...catch来包裹executor
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
从目前的代码中能够得到一些重要结论
- Promise的状态具有凝固性
- Promise可以在then方法第二个参数中进行错误处理
- Promise实例可以添加多个then处理场景
6.Promise then的链式调用
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise')
})
})
promise.then(data => {
console.log(data)
return data + ' next'
})
.then(data => {
console.log(data)
})
这段代码会在2S后输出promise,紧接着输出promise next.上面代码能看到Promise是支持链调用的,输出经过resolve处理过的值后,如果在then方法体的onfulfilled函数中同步显示返回新的值,则将会在Promise实例的then方法的onfulfilled函数中输出新的值。如果在then方法中返回一个Promise实例的话会如何?
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise')
})
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + 'next')
})
})
})
.then(data => {
console.log(data)
})
上面代码会在2s后输出promise,并在6s后输出promise next。由此可以知道一个promise实例的then方法的onfulfilled函数和onrejected函数是支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值,并且返回这个Promise实例或者这个非Promise实例的普通值将会传给下一个then方法的onfulfilled函数或者onrejected函数,这样就支持链式调用了。更改后的完整代码如下所示:
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFuncArr = []
this.onRejectedFuncArr = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFuncArr.forEach(func => {
func(value)
})
}
})
}
const reject = reason=> {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFuncArr.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.reason)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onfulfilledFuncArr.push(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})
this.onRejectedFuncArr.push(() => {
try {
let result = onrejected(this.reason)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
}
这里重点理解this.status==='pending'判断分支的逻辑,当使用Promise实例调用起then方法时,应该返回一个Promise的实例,返回的就是this.status===‘pengding’判断分支中返回的promise2.这个promise2应该什么时候被决议?应该是在异步处理结束之后,一次执行onfulfilledArray或onrejectedArray中的函数时。 在onfulfilledArray或onrejectedArray数组中的函数应该做些什么?主要死切换promise2的状态,并进行决议。
7.Promise链式调用的完善实现
我们继续实现then方法,以便返回一个Promise实例。对应场景的代码如下:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise')
})
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + 'next')
})
})
})
.then(data => {
console.log(data)
})
基于第一种onfulfilled和onrejected函数返回一个普通值的情况,要实现这种返回Promise实例的情况也不算太过困难。在之前实现let result = onfulfilled(this.value)语句和let result = onrejected(this.reason)语句中,使变量result由一个普通值变为一个Promise实例。换句话说就是result既可以是一个普通值,也可以是一个Promise实例,因此我抽象出了resolvePromise方法进行统一处理。改动后的代码如下
const resolvePromise = (promise2, result, resolve, reject) => {
}
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFuncArr = []
this.onRejectedFuncArr = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFuncArr.forEach(func => {
func(this.value)
})
}
})
}
const reject = reason=> {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFuncArr.forEach(func => {
func(this.value)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onfulfilledFuncArr.push(() => {
try {
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
this.onRejectedFuncArr.push(() => {
try {
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
}
resolvePromise函数接受4个参数。
- 1.promise2:返回的Promise实例。
- 2.onfulfilled或onrejected函数的返回值。
- 3.promise2的resolve方法。
- 4.promise2的rejected方法。
有了这些参数,我们就具备了抽象逻辑的必备条件。
const resolvePromise = (promise2, result, resolve, reject) => {
// 当result和Promise2相等的时候,也就是onfulfilled返回promise2时,执行reject
console.log(result, promise2)
if (result === promise2) {
reject(new TypeError('error due to circular reference'))
}
// 是否已经执行过onfulfilled或onrejected
let consumed = false
let thenable
if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似Promise类型
if (isComplexResult(result)) {
try {
thenable = result.then
// 判断返回值是不是Promise类型
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
} else {
resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
} else {
resolve(result)
}
}
我们看到第一步,resolvePromise第一步是对"死循环"进行处理,并在发生死循环时抛出错误。怎么理解这个处理?Promise实现规范中指出,其实出现"死循环"的情况如下。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ajin')
}, 2000)
promise.then(onfulfilled = data => {
console.log(data)
return onfulfilled(data)
})
.then(data => {
console.log(data)
})
})
接着,对于onfulfilled函数返回的结果result:如果result不是Promise实例,不是对象,也不是函数,而是一个普通值的话,则直接对promise2进行决议。
对于onfulfilled函数返回的结果result:如果result含有then方法,我们称这个方法为thenable,说明result是一个Promise实例,当执行该实例的then方法时,返回的结果还可能是一个Promise实例,也可能是一个普通值,因此还有地柜调用resolvePromise。若果还不明白为什么需要递归,可以看看如下的代码。
let a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ajin')
}, 2000)
})
a.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout( () => {
resolve(111 + data)
}, 4000)
})
.then(data => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + 11)
}, 4000)
})
})
})
.then(data => {
console.log(data)
})
以上代码会在2s时输出ajin,10S时输出111ajin11
此时的完整代码如下。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFuncArr = []
this.onRejectedFuncArr = []
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFuncArr.forEach(func => {
func(value)
})
}
})
}
const reject = reason=> {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFuncArr.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
const resolvePromise = (promise2, result, resolve, reject) => {
// 当result和Promise2相等的时候,也就是onfulfilled返回promise2时,执行reject
if (result === promise2) {
reject(new TypeError('error due to circular reference'))
}
// 是否已经执行过onfulfilled或onrejected
let consumed = false
let thenable
if (result instanceof Promise) {
if (result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似Promise类型
if (isComplexResult(result)) {
alert(1)
try {
thenable = result.then
// 判断返回值是不是Promise类型
if (typeof thenable === 'function') {
thenable.call(result, function(data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
} else {
resolve(result)
}
} catch(e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
} else {
resolve(result)
}
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
let promise2
if (this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onfulfilledFuncArr.push(value => {
try {
let result = onfulfilled(value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
this.onRejectedFuncArr.push(reason => {
try {
let result = onrejected(reason)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
}
8.Promise的静态方法和其他方法实现
我们实现的方法如下:
- Promise.prototype.catch
- Promise.resovle
- Promise.reject
- Promise.all
- Promise.race
Promise.prototype.catch
Promise.prototype.catch可以用来进行异常捕获,他的典型用法如下。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('ajin')
}, 2000)
})
promise1.then(data => {
console.log(data)
}).catch(error => {
console.log(error)
})
代码会在2s后输出阿金, 这种场景下他与一下代码是等价的。
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}
我们知道then()方法的第二个参数是进行异常的捕获,通过这个特性可以这样进行实现。
Promise.resolve
MDN上对于Promise.resolve(value)方法的介绍是这样的: Promise.resolve(value)方法返回一个已给定值解析后的Promise实例对象。 我们来看一个实例对象。
Promise.resolve('data').then(data => {
console.log(data)
})
console.log(1)
执行以上代码会先输出1再输出data,name实现Promise.resolve(value)也不难,具体代码如下。
Promise.resolve = function(data) {
return new Promise((resolve, reject)=> {
resolve(data)
})
}
// 顺便实现reject
Promise.reject = function(data) {
return new Promise((resolve, reject)=> {
reject(data)
})
}
Promise.all 实现
MDN上对于Promise.all的解释是这样的: Promise.all(iterable)方法返回一个Promise实例,此实例在Iterable参数内的所有Promise实例都完成(resolved)或参数中不包含Promise实例时完成回调(resolve);如果参数中的Promise实例中有一个失败(rejected),则此实例回调失败(reject),失败原因是的哥Promise实例失败的原因。
先看看下面的例子
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ajin')
}, 2000)
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('错误')
},2000)
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(33)
})
})
Promise.all([promise1, promise2]).then(data => {
console.log(data)
})
执行以上代码,会在2s后输出['ajin', '错误', '33']
以下是Promise.all的代码思路
Promise.all = function(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
let resultArr = []
let length = arr.length
for(let i = 0; i < length; i++) {
arr[i].then(data => {
resultArr.push(data)
if (resultArr.length === length) {
resolve(resultArr)
}
}, reject)
}
} catch(e) {
reject(e)
}
})
}
我们先对参数promiseArray的类型进行判断,对非数组类型参数抛出错误,Promise.all会返回一个Promise实例,这个实例将会在promiseArray中的所有Promise实例被决议后进行决议,决议结果如果是一个数组,这个数组存有promiseArray的所有Promise的决议值。
此实现的整体思路是一来一个for循环对PromiseArray进行遍历,同样的思路也可以实现promise.race。
Promise.race
先看看promise.race的用法
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ajin')
}, 2000)
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('错误')
},2000)
})
Promise.race([promise1, promise2]).then(data=> {
console.log(data)
})
实现的代码如下
Promise.race = function(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve, reject) => {
try {
const length = arr.length
for(let i = 0; i < length; i++) {
arr[i].then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
}