Promise是什么?
promise,承诺的意思。意思就是,JS执行线程 承诺
在将来的某个时刻执行某一块代码。
也就是说,你可以把一段将来才需要执行的代码,通过promise,把它放进异步任务队列中,在将来的某个时刻执行。So,promise就是用来写Javascript异步代码
的。
有什么用?
这就要从Javascript的异步任务
说起了。
比如在实际编程中经常需要使用ajax
向服务器请求数据,成功获取到数据后,才开始处理数据。于是代码分成获取数据
部分和处理数据
部分。在以前,处理数据
逻辑是通过回调函数来实现的,像这样:
function getData ( cb ) {
// 获取数据
let xhr = new XMLHttpRequest()
xhr.open('post', 'http://testdomain.com/api/getname', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) cb(xhr.responseText)
else alert(xhr.statusText)
}
xhr.send()
}
getData( data => {
// 用于处理数据的回调函数
alert(data)
} )
复制代码
现在假设有多个异步任务,且任务间有依赖关系(一个任务需要拿到另一个任务成功后的结果才能开始执行)的时候,回调的方式写出来的代码就会像下面这样:
getData1( data1 => {
getData2( data1, data2 => {
getData3( data2, data3 => {
getData4( data3, data4 => {
getData5( data4, data5 => {
// 终于取到data5了
})
})
})
})
})
复制代码
这种代码被称为回调地狱
或者回调金字塔
。如果逻辑复杂,阅读代码的时候跳来跳去,令人头秃。而且若是想要换一下执行顺序,代码修改起来就比较麻烦了。
Promise —— 优雅解决回调嵌套!
上面的代码如果用promise改写一下:
// 先把getData们都改写成返回promise对象的函数
getData1()
.then(getData2)
.then(getData3)
.then(getData4)
.then(getData5)
.then(data5 => {
// 取到最终的data5了
})
复制代码
这样线性的代码,流程清晰,便于阅读,要修改执行顺序也很容易。
Promise 好使吧?来来来,我们一起来好好了解下它。
先来了解一下 Promise 是怎么被使用的:
Promise 基本用法
let p = new Promise((resolve, reject) => {
// 做一些事情
// 然后在某些条件下resolve,或者reject
if (/* 条件 */) {
resolve()
} else {
reject()
}
}
p.then(() => {
// 如果p的状态被resolve了,就进入这里
}, () => {
// 如果p的状态被reject
})
复制代码
解释一下
生成Promise实例
构造函数接受一个函数作为参数 参数函数接受两个函数(resolve和reject)作为参数 resolve函数的作用是:把Promise实例 p 的状态修改为 fulfilled,然后执行then方法里注册的onFulfilled函数,也就是then方法的第一个参数 reject函数的作用是:把Promise实例 p 的状态修改为 rejected,然后执行then方法里注册的onRejected函数,也就是then方法的第二个参数 调用构造函数得到实例p的同时,作为参数的函数会立即执行 参数函数被调用时,实际上传给它的参数是在Promise类里面实现的两个函数resolve和reject
调用实例的then方法(调用then方法可以为实例 p 注册两个状态回调函数)
OK,了解到Promise
的用法,我们就满足了吗?我们会就这样停下学习的脚步吗?
Promise 的一些特性
Promise 的 状态
和值
Promise 的值是指状态改变时传递给回调函数的值。 Promise 对象存在以下三种状态:
i.Pending
(进行中)
ii.Fulfilled
(已成功)
iii.Rejected
(已失败)状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected,且状态改变之后不会在发生变化,会一直保持这个状态。
then
方法支持多次调用以注册多个回调函数then
方法支持链式调用
结合以上 Promise 的使用和特性,我们尝试自己实现一个 Promise 类。实现过程中,我们会重点讲解resolve
和then
这两个核心且常用的方法。
Promises A+
规范(promisesaplus.com/)规定了 Promise 的原理、源代码规范,我们的实现将基于这个规范。
我的Promise
// 判断变量否为function
const isFunction = variable => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (fn) {
if (!isFunction(fn)) {
throw new Error('Promise must accept a function as a parameter')
}
// 添加状态
this._status = PENDING
// 添加值
this._value = undefined
// 添加成功回调函数队列
this._fulfilledQueues = []
// 添加失败回调函数队列
this._rejectedQueues = []
// 执行handle
try {
// 传进来的参数函数会被立即执行,并将_resolve和_reject函数作为参数传给它
fn(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// then方法: 注册一个成功/失败回调函数,即,往promise对象的回调函数队列中添加回调函数
then (onFulfilled, onRejected) {}
// 添加catch方法
catch (onRejected) {}
// resovle函数的作用:修改promise对象的值和状态,执行then方法注册的成功回调函数队列
_resolve (val) {}
// reject函数的作用:修改promise对象的值和状态,执行then方法注册的失败回调函数队列
_reject (err) {}
}
复制代码
Emmm,差不多就是这样了。
我们再来看看Promise
类中的那些方法要怎么写。
then
// then方法: 注册一个成功/失败回调函数,即,往promise对象的回调函数队列中添加回调函数
then (onFulfilled, onRejected) {
if (onFulfilled) this._fulfilledQueues.push(onFulfilled)
if (onRejected) this._rejectedQueues.push(onRejected)
// 因为then方式支持链式调用,所以要返回一个promise对象,我们先简单的返回 promise 对象自己
return this
}
复制代码
catch
// 这个简单,就不多说了
catch (onRejected) {
return this.then(undefined, onRejected)
}
复制代码
_resolve
// resovle函数的作用:修改promise对象的值和状态,执行then方法注册的成功回调函数队列
_resolve (val) {
// 已经被resolve过的promise对象不能再次被resolve
if (this._status !== PENDING) return
// 为什么resolve 加setTimeout?
// 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
// 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
setTimeout(() => {
this._value = val
this._status = FULFILLED
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(val)
}
}, 0)
}
复制代码
_reject
// reject函数的作用:修改promise对象的值和状态,执行then方法注册的失败回调函数队列
_reject (err) {
if (this._status !== PENDING) return
setTimeout(() => {
this._value = err
this._status = REJECTED
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}, 0)
}
复制代码
Promise 的精髓
从表面上看,Promise 只是能够简化层层回调的写法,而实质上,Promise 的精髓是
状态
,用“维护状态”
、“传递状态”
的方式来使得回调函数能够及时调用,它比传递 callback 函数要简单、灵活的多。
还记得我们前面那段代码吗?
// 先把getData们都改写成返回promise对象的函数
getData1()
.then(getData2)
.then(getData3)
.then(getData4)
.then(getData5)
.then(data5 => {
// 取到最终的data5了
})
复制代码
我们来看看then
方法要怎么实现,才能支持上面的用法(getData1的值传给getData2,getData2传给getData3……)。我们之前写的还是太简单了,而且只是“return this”
也不支持这种状态的传递
。
then (onFulfilled, onRejected) {
let self = this
let promise2 // then要返回的新promise
promise2 = new Promise((resolve, reject) => {
let pushedOnFulfilled = () => {
setTimeout(() => {
// 当执行成功回调的时候,可能会出现异常,那就用这个异常作为promise2的错误的结果
try {
if (isFunction(onFulfilled)) {
let x = onFulfilled(self._value)
// x 有可能是Promise对象,也有可能是数字、字符串等普通类型的数据
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} else {
// 2.2.7.3规范:onFulfilled如果不是function则值会穿透,传入下一个then中
resolve(self._value)
}
} catch (e) {
reject(e)
}
}, 0)
}
let pushedOnRejected = () => {
setTimeout(() => {
try {
if (isFunction(onRejected)) {
let x = onRejected(self._value)
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} else {
// 2.2.7.4规范:onRejected如果不是function则错误会穿透,下一个promise也就是promise2会以相同的理由被reject
reject(self._value)
}
} catch (e) {
reject(e)
}
}, 0)
}
if (self._status === FULFILLED) {
pushedOnFulfilled()
} else if (self._status === REJECTED) {
pushedOnRejected()
} else if (self._status === PENDING) {
self._fulfilledQueues.push(pushedOnFulfilled)
self._rejectedQueues.push(pushedOnRejected)
}
})
return promise2
}
复制代码
then
讲完,考虑到resolve
的参数也有可能是Promise对象
,我们再来完善一下resolve
_resolve (val) {
if (this._status !== PENDING) return
setTimeout(() => {
let runFulfilled = () => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(this._value)
}
}
let runRejected = () => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(this._value)
}
}
/* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且参数promise的状态和值会传递给当前Promsie对象
*/
if (val instanceof Promise) {
val.then(v => {
this._value = v
this._status = FULFILLED
runFulfilled()
}, e => {
this._value = e
this._status = REJECTED
runRejected()
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled()
}
}, 0)
}
复制代码
到这里我们就把Promise
的核心then
和resolve
给讲得差不多了。
Promise的其它方法
最后补充Promise类的其它方法,由于没有太复杂的逻辑,我就话不多说,直接上代码了。
class Promise {
constructor (fn) {}
then (onFulfilled, onRejected) {}
catch (onRejected) {}
_resolve (val) {}
_reject (err) {}
/**********************************/
// 添加静态resolve方法
static resolve (value) {
// 如果参数是Promise实例,直接返回这个实例
if (value instanceof Promise) return value
return new Promise(resolve => resolve(value))
}
// 添加静态reject方法
static reject (value) {
return new Promise((resolve ,reject) => reject(value))
}
// 添加静态all方法
static all (list) {
return new Promise((resolve, reject) => {
/* 返回值的集合 */
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 数组元素有可能不是Promise实例,所以先调用Promise.resolve包装成Promise实例
Promise.resolve(p).then(res => {
values[i] = res
count++
// 所有状态都变成fulfilled时返回的Promise状态就变成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一个被reject时返回的Promise状态就变成rejected
reject(err)
})
}
})
}
// 添加静态race方法
static race (list) {
return new Promise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,返回的Promise的状态就跟着改变
Promise.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally (cb) {
return this.then(
value => Promise.resolve(cb()).then(() => value),
reason => Promise.resolve(cb()).then(() => { throw reason })
);
}
}
复制代码
本文使用 mdnice 排版