Promise 是什么?
Promise
是承诺的意思,在未来将兑现结果,可能成功也可能失败。在 Javascript 中,Promise
是一个内置对象,可以用来处理异步任务。比如一个数据请求,可能需要时间等待,但我们的程序不能卡住等待数据返回,于是通过 promise
提前设置好数据返回后将要做的操作,这样就不会卡住程序了,同时又能保证数据的处理。比如:
const promise = new Promise((resolve, reject) => {
// 这里模拟数据获取,1秒后拿到数据
setTimeout(() => {
resolve('data after one second')
}, 1000)
})
promise.then(res => {
// 这里拿到数据之后进行处理
console.log(res)
})
console.log('deal with other thing')
上面的例子,我们会先打印 deal with other thing
, 1 秒后 打印 data after one second
, 这就是 Promise
, “预约”将会做的事情,而不阻塞当前该做的事情。
Promise 为什么?
上面的 Promise
看起来平平无奇,为啥需要它呢?这得回顾过去,处理异步事件,靠什么方法实现。
没错,你一定已经知道了,就是回调函数。
在 Javascript ,“函数是一等公民”, 可以作为参数,可以作为返回值进行传递。那么我们可以把异步事件之后需要处理的操作,封装成一个函数,传递,在拿到数据之后进行调用,比如:
function handler(data) {
console.log(data);
}
function getDataOneSecond(callback) {
// 这里模拟数据获取,1秒后拿到数据
setTimeout(() => {
callback('data after one second')
}, 1000)
}
getDataOneSecond(handler)
console.log('deal with other thing')
上面的例子,同样实现了异步事件的处理而不阻塞程序。但是有明显的不足,比如代码复杂了,理解起来有点“绕”。再者,如果我们的异步事件不止一个,且有前后顺序处理的关系,那么事情就更加复杂起来,回调之后再回调,不用举例你也能想象代码会嵌套起来,令人恶心,所谓“回调地狱”。
相比回调函数,我们可以看到,Promise
明显比较符合事务前后关系,异步事件之后的操作,放到了 then
里面。
而在处理有前后关系的多个异步事件,Promise
也有对应的处理方式:then
的链式调用,比如:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data after one second')
}, 1000)
})
promise
.then(res => {
console.log(res)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + ' and another second')
}, 1000)
})
})
.then(res => {
console.log(res)
})
由于 then
本身还会返回一个新的 Promise
实例,所以可以接着调用 then
方法, 这样就避免了回调地狱的问题,以线性的方法编写我们的多个异步事件的代码了
Promise 怎么实现的?
Promise
本身是由 Javascript 引擎实现的, 属于微任务。在每一次的事件循环中,等宏任务执行之后,会处理微任务、清空微任务队列。then
方法会在微任务队列加入一个事件。
那么让你模拟创建一个构造函数,实现和 Promise
类似的功能,将怎么实现呢?
这里我们来实现一个最基础简化版本的伪 Promise
:
// 构造函数
function MyPromise(excutor) {
this.state = 'PENDING'; // 'FULFILLED' 'REJECTED'
this.value = null;
this.reason = null;
this.taskResolveList = [];
this.taskRejectList = [];
const self = this;
function resolve (value) {
if (self.state === 'PENDING') {
self.value = value
self.state = 'FULFILLED'
if (self.taskResolveList.length) {
self.taskResolveList.forEach(cb => cb())
}
}
}
function reject(reason) {
if (self.state === 'PENDING') {
self.reason = reason
self.state = 'REJECTED'
if (self.taskRejectList.length) {
self.taskRejectList.forEach(cb => cb())
}
}
}
try {
excutor(resolve, reject)
} catch(error) {
reject(error)
}
}
MyPromise.prototype.then = function (fulfilled, rejected) {
if (this.state === 'FULFILLED') {
// 这里应该放到微任务掉列里面,但无法实现,以同步执行代替
fulfilled(this.value)
}
if (this.state === 'REJECTED') {
// 这里应该放到微任务掉列里面,但无法实现,以同步执行代替
rejected(this.reason)
}
if (this.state === 'PENDING') {
this.taskResolveList.push(() => {
fulfilled(this.value)
})
this.taskRejectList.push (() => {
rejected(this.reason)
})
}
}
// 测试同步代码
new MyPromise((resolve, reject) => {
resolve('打印同步代码')
}).then(res => {
console.log(res)
})
// 测试异步代码
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('1秒之后打印异步代码')
}, 1000)
}).then(res => {
console.log(res)
})
我们可以发现,这是一个发布订阅模式:收集依赖 -> 触发通知 -> 取出依赖执行。当然这是最基础的版本,并且无法模拟微任务,所以无法实现和真是 Promise
的一样的执行顺序。更加真实和详细的手写 Promise
, 可以参考这篇文章
Promise api
Promise
原型还有很多其他api:
Promise.prototype.catch()
Promise.prototype.race()
Promise.prototype.all()
Promise.prototype.any()
Promise.prototype.allSettled()
Promise.prototype.finally()
Promise.prototype.resolve()
Promise.prototype.reject()
具体用法可以参考阮一峰老师的 ES6教程,讲得通俗易懂。