本文将以图解和源码相结合的方式介绍Promise的原理,主要以Promises/A+规范为主,帮助大家更好地理解Promise。
下文中提到的源码非官方源码,而是以这份模拟源码为主,原因是相比官方源码,它更容易理解。该源码能够跑通Promises/A+所有测试用例,所以功能是一样的。
Promise的本质
Promise代表的是一个承诺,表示某个异步操作暂时还没有结果,但承诺在将来的某个时候会告知用户结果。
let promise1 = new Promise((resolve, reject) => {
try {
// 一些异步操作,比如setTimeout,或者异步请求
setTimeout(() => {
resolve(1)
}, 1000)
} (err) {
// 出错时告知错误信息
reject(err)
}
})
用图形来表示,每个promise实例内部都有两个接口,resolve和reject,通过回调函数参数的形式暴露给外面,把调用权交个外面,相当于告诉用户:“嘿哥们,请放心执行你的任务,我一直都在,有结果后告诉我一声,成功请call resolve,失败请call reject”。
promise对象有三种状态:pending、fulfilled和rejected。
pending - The initial state of a promise.
fulfilled - The state of a promise representing a successful operation.
rejected - The state of a promise representing a failed operation.
只能由pending到fulfilled或者从pending到rejected,而且状态不可逆,我们规定pending为“未完结”状态,fulfilled和rejected为“完结”状态。
一起来看下Promise构造函数的源码:
function Promise(f) {
this.result = null
this.state = PENDING
...
let ignore = false
let resolve = value => {
if (ignore) return
ignore = true
resolvePromise(this, value, onFulfilled, onRejected)
}
let reject = reason => {
if (ignore) return
ignore = true
onRejected(reason)
}
try {
f(resolve, reject)
} catch (error) {
reject(error)
}
}
resolve和reject函数是构造函数内的局部函数,作为两个参数传递给 f 执行函数。另外有一个ignore局部变量,保证resolve和reject内的逻辑只会执行一次。另外state属性代表promise当前的状态,而result代表promise完结的结果。当fulfilled时,代表成功结果,而rejected时代表失败原因。
then方法
then是promise最关键的方法,用户通过它来获取异步结果,它拥有两个函数参数,分别获取成功结果和失败原因。
promise1.then((data) => {
console.log('成功:', data)
}, (err) => {
console.error('失败:', err)
})
按照Promise的规范,then方法会返回一个新的Promise实例,我们假设为promise2。promise1中对promise2的resolve和reject进行了引用,这是promise链式调用的关键,我们后面讲。
先看then方法的源码:
Promise.prototype.then = function(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
let callback = { onFulfilled, onRejected, resolve, reject }
if (this.state === PENDING) {
this.callbacks.push(callback)
} else {
setTimeout(() => handleCallback(callback, this.state, this.result), 0)
}
})
}
代码很简单,通过一个callback对象保存当前promise的onFulfilled、onRejected函数和下个promise的resolve、reject函数。如果promise的状态还未完结,就暂存到callbacks数组;如果状态已完结,则立即用完结状态的结果处理callback。
图解如下:
链式调用
上文提到then方法会返回一个新的promise对象,所以后面的then方法就是第二个promise的方法,以此类推,就形成了一条promise链,图解如下:
有点像单向数据列表有没有:
单向数据链表
当promise1的resolve被调用时,先判断和调用onFulfilled函数,而当promise1的reject被调用时,则会判断和调用onRejected函数。
处理完onFulfilled或onRejected逻辑后,将处理结果传递给promise2的resolve或者reject,如此,promise2的状态也进行了联动改变,这就是promise链式调用的本质。
promise同步任务
promise一般用于异步任务,但是也支持同步任务,可以猜猜下面代码的运行结果:
new Promise((resolve, reject) => {
console.log(1)
resolve(1)
})
.then((result) => {
console.log(2)
})
console.log(3)
// 输出结果
1
3
2
可以看到,即使是同步resolve,onFulfilled函数也未能同步执行,原因是使用了setTimeout,保证了onFulfilled的异步执行。上源码:
Promise.prototype.then = function(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
let callback = { onFulfilled, onRejected, resolve, reject }
if (this.state === PENDING) {
this.callbacks.push(callback)
} else {
// 使用setTimeout保证异步
setTimeout(() => handleCallback(callback, this.state, this.result), 0)
}
})
}
const transition = (promise, state, result) => {
if (promise.state !== PENDING) return
promise.state = state
promise.result = result
// 状态改变时也用了setTimeout保证异步
setTimeout(() => handleCallbacks(promise.callbacks, state, result), 0)
}
resolve promise实例
熟悉promise的同学应该都知道,onFulfilled函数可以返回任何有效的js值,包括: undefined、promise实例和thenable对象,这里重点讲下promise实例的情况,thenable的原理一样,读者可自行理解。测试代码如下:
let promise1 = new Promise((resolve, reject) => {
try {
// 一些异步操作,比如setTimeout,或者异步请求
setTimeout(() => {
resolve(1)
}, 1000)
} (err) {
// 出错时告知错误信息
reject(err)
}
})
let externalPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('task result')
}, 1000)
})
promise1.then((data) => {
return externalPromise
}).then((data) => {
console.log('成功:', data)
})
其中promise1.then生成了promise2, 而第二个then生成了promise3。promise1的onFulfilled返回了externalPromise对象,当externalPromise fulfill时,就会调用promise2的resolve函数,从而触发promise2的resolve流程,从而调用promise2的onFulfilled函数,由此,又回到了原来的resolve链路。
有点像婚姻状况出现了点小插曲,最后还是回到了正轨^_^。
resolve自己
在Promises/A+的规范里,是不允许resolve promise自己的。为什么?
先看源码:
function Promise(f) {
...
let ignore = false
let resolve = value => {
if (ignore) return
ignore = true
resolvePromise(this, value, onFulfilled, onRejected)
}
...
try {
f(resolve, reject)
} catch (error) {
reject(error)
}
}
const resolvePromise = (promise, result, resolve, reject) => {
...
if (isPromise(result)) {
return result.then(resolve, reject)
}
...
resolve(result)
}
当resolve的value为本身时,resolvePromise函数的 promise === result,promise的onFulfilled和onRejected传给result的then方法,等待result的结果,这就导致了自己等自己,进入了死循环。测试一下:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(promise)
}, 0)
})
promise.then((result) => {
console.log('resolve:', result)
})
// 输出结果
VM783:3 Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
at <anonymous>
at <anonymous>:3:9
“奇怪”的reject流程
在promise链式调用里,reject跟resolve的流程不太一样,当我刚接触promise时还不太理解。reject流程的表现为,当头部promise rejected时,后续的promise都会reject,直到遇到第一个拥有onRejected的promise,用onRejected的执行结果,resolve后面的promise,说直白点就是状态“反转”了。
new Promise((resolve, reject) => {
setTimeout(() => {
reject(1)
}, 10)
})
.then((result) => {
console.log('onResolved_1', result)
return 'onResolved_1'
}, (reason) => {
console.log('onRejected_1', reason)
return 'onRejected_1'
})
.then((result) => {
console.log('onResolved_2', result)
return 'onResolved_2'
}, (reason) => {
console.log('onRejected_2', reason)
return 'onRejected_2'
})
.then((result) => {
console.log('onResolved_3', result)
return 'onResolved_3'
}, (reason) => {
console.log('onRejected_3', reason)
return 'onRejected_3'
})
.catch(() => {
console.log('catch', reason)
})
// 输出结果
onRejected_1 1
onResolved_2 onRejected_1
onResolved_3 onResolved_2
我们可以看到经过第一个then的onRejected后,触发了第二个then的onFulfilled函数,但是收到的是第一个then的onRejected返回的结果。还是看源码比较形象:
const handleCallback = (callback, state, result) => {
let { onFulfilled, onRejected, resolve, reject } = callback
try {
if (state === FULFILLED) {
isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
} else if (state === REJECTED) {
isFunction(onRejected) ? resolve(onRejected(result)) : reject(result)
}
} catch (error) {
reject(error)
}
}
我们可以看到当state === FULFILLED时,如果onRejected为函数时,执行onRejected后,调用了下个promise的resolve接口。
猜测这么设计的目的是给用户一个错误处理的机会,但又不影响后面的链式调用。但需要特别注意的是第二个then中的onResolved中需要判断参数result的类型,否则代码容易出错。
这里,应该也有小伙伴会有疑问,为什么我最后的catch回调函数没有执行?
catch的本质就是onFulfilled函数为null的then函数,参考官方实现。
// https://github.com/then/promise/blob/master/src/es6-extensions.js
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
之前也提到了,中间promise的状态“反转”了,后面的promise变成了resolve状态,所以最后的onRejected也理所当然不会被调用。
链式调用中的异常处理
经常会看到这类面试题,请说出以下代码的运行结果:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 10)
})
.then((result) => {
console.log('onResolved_1', result)
throw new Error('custom error')
}, (reason) => {
console.log('onRejected_1', reason)
return 'onRejected_1'
})
.then((result) => {
console.log('onResolved_2', result)
return 'onResolved_2'
}, (reason) => {
console.log('onRejected_2', reason)
return 'onRejected_2'
})
// 运行结果
onResolved_1 1
VM713:17 onRejected_2 Error: custom error
大部分同学应该猜对了结果,但原因是什么?一起来看源码可能更能理解:
const handleCallback = (callback, state, result) => {
let { onFulfilled, onRejected, resolve, reject } = callback
try {
if (state === FULFILLED) {
isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
} else if (state === REJECTED) {
isFunction(onRejected) ? resolve(onRejected(result)) : reject(result)
}
} catch (error) {
reject(error)
}
}
这里的onFulfilled和onRejected就是第一个then的两个参数,而resolve和reject就是第一个then函数新生成promise对象的resolve和reject接口,我们可以看到,外侧包了一层try...catch
,一旦内部代码报错,就会调用下一个promise的reject接口,并不会调用当前promise的onRejected函数。
搞清楚catch的本质以及链式调用中的异常处理,结合自己多年的经验,给出个人的最佳实践:
- then方法的onRejected函数留空,由最后的catch方法统一处理异常
- catch后不再添加then方法
let syncTask = (resolve, reject) => {}
let handle1 = (res) => {...}
let handle2 = (res) => {...}
let handle3 = (res) => {...}
...
let onError = (err) => { console.log('onError:', err) }
let p = new Promsie(syncTask)
p
.then(handle1)
.then(handle2)
.then(handle3)
.catch(onError)
Promise.resolve的实现
我们经常会看到以下代码:
Promise.resolve(1)
.then((result) => {
console.log('result:', result)
})
Promise.resolve并不在Promises/A+的规范里,但通过上面内容的学习,懂得promise的原理后,自己实现一个也就非常简单了,其实就是返回一个promise实例:
Promise.resolve = function (value) {
if (value instanceof Promise) return value;
return new Promise((resolve, reject) => {
resolve(value)
})
};
详细实现请看官方实现:github.com/then/promis…
最后
以上就是我理解的Promise,希望能帮助正在学习Promise的同学更好地理解。最后,附上一套Promise问答题,结合上述内容,看看自己是否都能答对。