一、问题引入
不知你是否有以下疑问:
- Promise是什么来的?
- Promise是为了解决什么问题而引入进来的?
- 在没有Promise前,我们是如何写代码的?
- Promise是如何使用的?
- Promise在事件循环中的执行过程是怎么样的?
- 为什么promise在浏览器和node宿主环境上执行有差异?
- 如何手写一个Promise?
- Promise和async/await有什么关联?
如果这些问题你还有一丢丢困惑,那么欢迎继续往下看.
二、Promise出现缘由
众所周知的是,Javascript一个既有同步操作又有异步操作的语言,异步操作包含setTimeout,ajax等,而业务需求中经常会有通过ajax去请求后端数据的场景。
如果有多次ajax请求时,代码的结构是怎么样的呢? 首先,我们先用setTimeout来模拟一次ajax异步请求
function ajax(cb) {
setTimeout(function() {
cb('返回')
}, 1000)
}
那么在多次ajax请求场景下,代码结构会变成:
ajax(function(msg1) {
ajax(function(msg2) {
ajax(function(msg3) {
ajax(function(msg4) {
ajax(function(msg4) {
})
})
})
})
})
显而易见的是,这样的代码结构可读性差,可维护性差,只能在回调里处理异常,大家也把这种代码结构称为回调地狱。
那么为了解决这个问题,ES6把Promise纳入了规范中,作为一种异步编程的解决方案,比传统的回调函数、事件监听、观察者模式等解决方案要更合理。
参考 异步解决方案
三、什么是Promise
Promise到底是如何解决异步编程问题的,怎么使用的,我们继续往下看:
Promise代码结构
我们先来看一下Promise的代码结构
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(1000).then((value) => {
console.log(value);
return timeout(2000);
}).then((value) => {
console.log(value);
return timeout(3000);
}).then((value) => {
console.log(value);
});
从代码来看,Promise可通过then链式调用来处理多个异步请求,成功则触发resolve函数,失败则触发reject函数。 而仅仅这些还不够,Promise还需要处理函数报错,不能改变状态等,所以就有了Promises/A+规范。
Promises/A+规范
- Promise 存在三个状态(state)pending、fulfilled、rejected
- pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态)
- 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
- 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
- new Promise((resolve, reject)=>{resolve(value)}) resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变。
- new Promise((resolve, reject)=>{reject(reason)}) reject为失败,接收参数reason,状态改变为rejected,不可再次改变。
- 若是executor函数报错 直接执行reject();
这只是其中一部分,更多可参考 promises/A+规范
Promise的API
API | 解释 |
---|---|
Promise.prototype.then() | 状态改变时的回调函数 |
Promise.prototype.catch() | 用于指定发生错误时的回调函数 |
Promise.prototype.finally() | 最终都会执行的回调函数 |
Promise.prototype.all() | 用于执行多个Promise实例(所有操作状态变更相同才更改状态) |
Promise.prototype.race() | 用于执行多个Promise实例(有一个操作状态变更就更改状态) |
Promise.prototype.allSettled() | 用于执行多个Promise实例(所有操作状态都变更才更改状态) |
更多可参考 promise介绍
四、Promise在事件循环中的执行过程
要理解promise在事件循环中的流程,我们需要先理解一下消息队列和事件循环、宏任务和微任务等概念:
消息队列和事件循环(event loop)
首先我们知道js是单线程执行的,那么js在单线程中的执行场景是怎么样的?
单线程下执行任务过程
假如我们有一系列的任务需要做,比如
function task() {
const t1 = 1 + 2; //任务一
const t2 = 2 + 2; //任务二
const t3 = 3 + 2; //任务三
const sum = t1 + t2 + t3; //任务四
}
从这些执行代码中,浏览器会把所有代码放进主线程里,等要执行时,会按照顺序一次执行任务,任务执行完就退出线程。
如果在执行任务过程中,要接受并执行新的任务,那应该怎么办呢?
事件循环和消息队列
要想在执行任务过程中,能够执行新的任务,浏览器就需要引入事件循环和消息队列,用来判断是否有新的任务要执行。具体需要引入
- 事件循环,需要添加for语句,判断任务是否执行完,如果还未执行,就继续执行
- 消息队列,主线程可以处理其他进程发过来的任务,那么就需要用消息队列存着,先进先出的
那么消息队列中的任务都是一样的优先级吗?
宏任务和微任务
消息队列里的任务有很多种类型,比如输入事件,文件读写,js定时器,promise,websock等等 如果对这些任务进行分类,可以分为宏任务和微任务。
宏任务
宏任务是浏览器在执行的,包括了
- 渲染事件(如解析 DOM、计算布局、绘制)
- 用户交互事件(如鼠标点击、滚动页面、放大缩小等)
- script标签中的运行代码
- 网络请求完成、文件读写完成事件。
- setTimeout定时任务
可以看出,宏任务的时间粒度比较大,执行时间不能精确控制,不满足某些实时性的需求,比如监听DOM变化的需求,为此引入了微任务。
微任务
微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束后,当前宏任务结束之前。包括了
- promises:Promise.then、Promise.catch、Promise.finally
- MutationObserver
- queueMicrotask
- process.nextTick (node环境下)
promise是属于微任务的,那么promise和微任务的关联是怎么样的?
promise和微任务
为了理解promise和微任务之间的关系,我们也引入setTimeout这个宏任务来进行比较,先看以下代码
console.log('宏任务1')
setTimeout(() => {
console.log('宏任务2')
})
Promise.resolve().then(() => {
console.log('微任务1')
})
console.log('宏任务3')
放在浏览器中执行,结果会是:
宏任务1
宏任务3
微任务1
宏任务2
过程分析如下:
- 整个js脚本为一个宏任务来执行,先打印宏任务1和宏任务3
- setTimeout作为一个宏任务放入宏任务队列中
- promise作为一个微任务放入微任务队列中
- 本次js脚本宏任务执行结束之前,检查微任务,发现有一个微任务,执行,打印微任务1
- 进入下一个宏任务,发现setTimeout,执行,打印宏任务2
可以看出,宏任务和微任务的执行时机是不一样的,promise是作为微任务的角色在事件循环机制中执行的。
五、手写promise
Promise是V8提供的,内部的方法我们无法看得到,为了能更加清晰的理解promise原理,我们来手写一下promise,首先看一下promise实例
promise实例
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'done');
});
}
timeout().then(function(value) {
console.log(value)
}, function(error) {
console.log(error)
})
timeout().then(function(value) {
console.log(value)
return timeout()
}).then(function(value) {
console.log(value)
// 返回字符串,而不是promise
return 'success'
}).then(function(value) {
console.log(value)
// 使用resolve静态方法
return Promise.resolve('resolve')
}).then(function(value) {
console.log(value)
}).catch(function(err) {
console.log(err)
})
我们分析一下代码,会发现:
- Promise是一个类,需要传入一个exec执行函数,这个执行函数会马上执行
- Promise有三个状态
- pendding 等待
- fullfilled 完成
- rejected 失败
- 状态不可以撤回,只能从Pendding到Fullfilled,或者从Pendding到Rejected,状态一旦更改就不可以修改
- Promise可以执行多次then方法,会保存对应的回调函数等待合适时机调用
- Promise的then方法可以实现链式调用
- Promise有try-catch处理,如果代码报错则状态变为Rejected
promise实现代码
1. 创建class并传入exec函数,函数马上执行
class MyPromise {
constructor(exec) {
exec()
}
}
2. 传入resolve和reject参数作为回调函数
class MyPromise {
constructor(exec) {
exec(this.resolve, this.reject)
}
resolve() {
}
reject() {
}
}
3. 初始化Promise状态,resolve和reject函数添加状态修改逻辑
class MyPromise {
constructor(exec) {
this.state = 'pendding'
exec(this.resolve, this.reject)
}
resolve() {
this.state = 'fullfilled'
}
reject() {
this.state = 'rejected'
}
}
4. Promise状态只能从pendding到fullfilled或rejected
class MyPromise {
constructor(exec) {
this.state = 'pendding'
exec(this.resolve, this.reject)
}
resolve() {
if(this.state === 'pendding') {
this.state = 'fullfilled'
}
}
reject() {
if(this.state === 'pendding') {
this.state = 'rejected'
}
}
}
5. 保存异步返回的回调值
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
exec(this.resolve, this.reject)
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
}
}
}
6. 添加then方法
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
exec(this.resolve, this.reject)
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
}
}
then(onResolve, onReject) {
if(this.state === 'fullfilled') {
onResolve(this.value)
}
if(this.state === 'rejected') {
onReject(this.value)
}
}
}
7. 支持多个then方法,保存回调方法等待状态变更时调用
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
exec(this.resolve, this.reject)
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while(this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while(this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
if(this.state === 'fullfilled') {
onResolve(this.value)
}
if(this.state === 'rejected') {
onReject(this.value)
}
if(this.state === 'pendding') {
this.resolveCallback.push(onResolve)
this.rejectedCallback.push(onReject)
}
}
}
8. 支持then链式调用(重点)
如果要实现链式调用,那么then方法要返回一个promise对象。但是上一个then方法中可能返回promise对象,也可能返回其他类型,那么我们需要判断类型,如果非promise,则封装成promise对象
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
exec(this.resolve, this.reject)
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while(this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while(this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
return new MyPromise((resolve, reject) => {
if(this.state === 'fullfilled') {
const x = onResolve(this.value)
if(x instanceof MyPromise) {
// 如果是promise对象,则调用then方法
// x.then(value => resolve(value), err => reject(err))
// 可简写为
x.then(resolve, reject)
}else {
resolve(x)
}
}
if(this.state === 'rejected') {
onReject(this.value)
}
if(this.state === 'pendding') {
this.resolveCallback.push(onResolve)
this.rejectedCallback.push(onReject)
}
})
}
}
9. 继续处理rejected和pendding状态下的链式调用
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
exec(this.resolve, this.reject)
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while(this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while(this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
return new MyPromise((resolve, reject) => {
if(this.state === 'fullfilled') {
const x = onResolve(this.value)
resolvePromise(x, resolve, reject)
}
if(this.state === 'rejected') {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
}
if(this.state === 'pendding') {
this.resolveCallback.push((value) => {
const x = onResolve(value)
resolvePromise(x, resolve, reject)
})
this.rejectedCallback.push((value) => {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
})
}
})
}
}
// 把判断promise对象的方法封装出来
function resolvePromise(x, resolve, reject) {
if(x instanceof MyPromise) {
// 如果是promise对象,则调用then方法
// x.then(value => resolve(value), err => reject(err))
// 可简写为
x.then(resolve, reject)
}else {
resolve(x)
}
}
10. 添加try-catch和优化then参数为可传参数
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
try {
exec(this.resolve, this.reject)
}catch(err) {
this.reject(err)
}
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while(this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while(this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : value => value
onReject = typeof onReject === 'function' ? onReject : err => {throw err}
return new MyPromise((resolve, reject) => {
if(this.state === 'fullfilled') {
try {
const x = onResolve(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
}
if(this.state === 'rejected') {
try {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
}
if(this.state === 'pendding') {
this.resolveCallback.push((value) => {
try {
const x = onResolve(value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
})
this.rejectedCallback.push((value) => {
try {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
})
}
})
}
}
// 把判断promise对象的方法封装出来
function resolvePromise(x, resolve, reject) {
if(x instanceof MyPromise) {
// 如果是promise对象,则调用then方法
// x.then(value => resolve(value), err => reject(err))
// 可简写为
x.then(resolve, reject)
}else {
resolve(x)
}
}
11. 支持 Promise.resolve 还有 Promise.reject 的用法
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
try {
exec(this.resolve, this.reject)
}catch(err) {
this.reject(err)
}
}
resolve(value) {
if(this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while(this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject(value) {
if(this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while(this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : value => value
onReject = typeof onReject === 'function' ? onReject : err => {throw err}
return new MyPromise((resolve, reject) => {
if(this.state === 'fullfilled') {
try {
const x = onResolve(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
}
if(this.state === 'rejected') {
try {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
}
if(this.state === 'pendding') {
this.resolveCallback.push((value) => {
try {
const x = onResolve(value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
})
this.rejectedCallback.push((value) => {
try {
const x = onReject(this.value)
resolvePromise(x, resolve, reject)
}catch(err) {
reject(err)
}
})
}
})
}
// resolve 静态方法
static resolve (value) {
// 如果传入 MyPromise 就直接返回
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => {
resolve(value)
})
}
// reject 静态方法
static reject (err) {
return new MyPromise((resolve, reject) => {
reject(err)
});
}
}
// 把判断promise对象的方法封装出来
function resolvePromise(x, resolve, reject) {
if(x instanceof MyPromise) {
// 如果是promise对象,则调用then方法
// x.then(value => resolve(value), err => reject(err))
// 可简写为
x.then(resolve, reject)
}else {
resolve(x)
}
}
完成手写promise后,我们怎么判断写的是否符合规范呢?
验证promise代码
1. 全局安装promises-aplus-tests
npm install promises-aplus-tests -g
2. 加入deferred
MyPromise.deferred = function () {
var result = {}
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = MyPromise;
3. 启动命令看测试结果
发现有很多报错的,我们来逐个解决:
问题1:2.2.1和2.2.2不通过
从规范来看,是说没有处理then方法的可选参数,可是我明明有处理了,很奇怪,我们看看这个npm包的处理逻辑
问题出在adapter.rejected方法的定义上,debug后,需要把MyPromise的resolve和reject方法改为箭头函数才能解决,否则会读不到this.state
问题2:2.2.2 在promise完成之前不能调用它
从规范来看,需要在promise完成之前才能使用,这时,我们就可以用微任务来解决这个问题
问题3:2.3.* 都还没处理
从截图看,promise和x如果是相同的对象,会出问题,还有一些其他的要求,是我们的resolvePromise写的不够完善,优化下代码
// 封装一下函数
function resolvePromise(promise, x, resolve, reject) {
// 如果相等了,说明返回自己,会引起循环调用问题
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'))
}
if (typeof x === 'object' || typeof x === 'function') {
// x 为 null 直接返回
if (x === null) {
return resolve(x)
}
let then
try {
// 把 x.then 赋值给 then
then = x.then
} catch (error) {
// 如果出错就拒绝 promise
return reject(error)
}
// 如果 then 是函数
if (typeof then === 'function') {
let called = false
try {
then.call(
x, // this 指向 x
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
y => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量 called
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
r => {
if (called) return
called = true
reject(r)
})
} catch (error) {
// 如果调用 then 方法抛出了异常 error:
// 如果 resolvePromise 或 rejectPromise 已经被调用,直接返回
if (called) return
// 否则以 error 为据因拒绝 promise
reject(error)
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x)
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x)
}
}
4. 再次测试
完美解决,附上完整代码如下
class MyPromise {
constructor(exec) {
this.state = 'pendding'
this.value = null
this.resolveCallback = []
this.rejectedCallback = []
try {
exec(this.resolve, this.reject)
} catch (err) {
this.reject(err)
}
}
resolve = (value) => {
if (this.state === 'pendding') {
this.state = 'fullfilled'
this.value = value
while (this.resolveCallback.length) {
this.resolveCallback.shift()(this.value)
}
}
}
reject = (value) => {
if (this.state === 'pendding') {
this.state = 'rejected'
this.value = value
while (this.rejectedCallback.length) {
this.rejectedCallback.shift()(this.value)
}
}
}
then(onResolve, onReject) {
onResolve = typeof onResolve === 'function' ? onResolve : value => value
onReject = typeof onReject === 'function' ? onReject : err => { throw err }
const p2 = new MyPromise((resolve, reject) => {
if (this.state === 'fullfilled') {
queueMicrotask(() => {
try {
const x = onResolve(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onReject(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.state === 'pendding') {
this.resolveCallback.push((value) => {
queueMicrotask(() => {
try {
const x = onResolve(value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
this.rejectedCallback.push((value) => {
queueMicrotask(() => {
try {
const x = onReject(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
})
return p2
}
// resolve 静态方法
static resolve(value) {
// 如果传入 MyPromise 就直接返回
if (value instanceof MyPromise) {
return value
}
return new MyPromise(resolve => {
resolve(value)
})
}
// reject 静态方法
static reject(err) {
return new MyPromise((resolve, reject) => {
reject(err)
})
}
}
// 封装一下函数
function resolvePromise(promise, x, resolve, reject) {
// 如果相等了,说明返回自己,会引起循环调用问题
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'))
}
if (typeof x === 'object' || typeof x === 'function') {
// x 为 null 直接返回
if (x === null) {
return resolve(x)
}
let then
try {
// 把 x.then 赋值给 then
then = x.then
} catch (error) {
// 如果出错就拒绝 promise
return reject(error)
}
// 如果 then 是函数
if (typeof then === 'function') {
let called = false
try {
then.call(
x, // this 指向 x
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
y => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量 called
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
r => {
if (called) return
called = true
reject(r)
})
} catch (error) {
// 如果调用 then 方法抛出了异常 error:
// 如果 resolvePromise 或 rejectPromise 已经被调用,直接返回
if (called) return
// 否则以 error 为据因拒绝 promise
reject(error)
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x)
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x)
}
}
MyPromise.deferred = function () {
var result = {}
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = MyPromise
六、问题回复
- Promise是一种异步代码编写规范
- 为了解决异步代码引起的回调地狱问题
- 通过回调函数、事件监听等异步编程方案来写代码
- 可参考文章中的相关例子
- 可参考文章中的事件循环和微任务部分
- 因为浏览器和node宿主环境的事件循环有差异
- 可参考文章中手写Promise代码部分
- async/await只是语法糖,本质是通过promise来实现