ES6-Promise-期约
1. 期约
1.1 期约规范及基础
新增的引用类型 Promise,可以通过 new 操作符来实例化。Promise 内部包含异步操作
let p = new Promise(() => {})
setTimeout(console.log, 0, p) // Promise <pending>
- Promise 有三种状态:
待定(Pending)、兑现(Fulfilled)和拒绝(Rejected)
,且 Promise 必须为三种状态之一,只有异步操作的结果可以决定当前状态,任何其他操作都无法改变状态。 - Promise 状态只能由 Pending 转为其他两种状态,改变之后不会再发生变化。
- Pending 变为 Fulfilled 后会得到一个私有 value,变为 Rejected 会得到一个私有 reason,后面的异步代码会接收这个值。
Promise 的执行过程:
- 初始化 Promise 状态(Pending)
- 立即执行 Promise 中传入的 fn 函数,将 Promise 内部 resolve、reject 函数作为参数传给 fn,按事件机制时机处理
- 执行 then(...)注册回调处理数组
- Promise 的关键是要保证 then 方法传入的参数 onFulfilled 和 onRejected,必须在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
真正的链式 Promise 是指在当前 promise 达到 fulfilled 状态后,即开始进行下一个 promise。
2.2 执行函数控制期约状态
Promise 的状态是私有的,状态变化只能在期约的执行器函数中完成。Promise 提供了两个函数来调用改变状态:resolve()
和reject()
。resolve 会把状态切换为 Fulfilled,reject 会把状态切位 Rejected,且会抛出错误。
let p1 = new Promise((resolve,reject) => resolve())
setTimeout(console.log, 0, p1) // Promise <resolved>
let p2 = new Promise(reject => reject())
setTimeout(console.log, 0 ,p2) // Promise <rejected>
2.3 期约方法
then 链式操作的用法
let p = new Promise(resolve => resolve('开始了'))
p.then(data => {
console.log(data)
return '到你了'
}).then(data => {
console.log(data)
})
reject 的用法
Promise 的状态置位 rejected,这样就可以在 then 中捕获,执行失败的回调。then 方法接收两个参数,第一个对应 resolve 回调,第二个对应 reject 回调。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.ceil(Math.random() * 10)
num <= 5 ? resolve(num) : reject('太大了')
}, 0)
})
p.then(
res => {
console.log('resolved', res)
},
err => {
console.log('rejected', err)
}
)
catch 的用法
同 then 中第二个参数一样,指定 jeject 的回调,还可以抛出 resolve 毁掉中的异常。
p.then(data => {
console.log(happy) // happy未定义
}).catch(err => {
console.log('catch',err)
})
all 的用法
all 提供了并行执行异步操作的能力,接收一个数组参数,里面的值最终都算返回 Promise 对象,在所有异步操作执行完后才执行回调。只有所有方法都成功是才成功,只要有一个失败就变为 Rejected 状态。
const p1 = Promise.resolve(1)
const p2 = new Promise(resolve => {
setTimeout(resolve,100, 'foo')
})
const p3 = 3
Promise.all([p1,p2,p3]).then(values => {
console.log(values) // Array [1, 'foo', 3]
})
race 的用法
race 接收一个数组参数,谁先执行返回谁
function requestFast() {
return new Promise(resolve => {
setTimeout(() => {
resolve('成功了')
},3000)
})
}
function requestSlow(){
return new Promise(reject=> {
setTimeout(() => {
reject('失败了')
},2000)
})
}
Promise.race([requestFast(),requestSlow()]).then(data => {
console.log(data)
}).catch(err => {
console.log(err)
})
2. 手写 Promise
1. 实现 resolve 与 reject
Promise 的初始状态是pending
,resolve和reject要绑定this
,保证 resolve 和 reject 的 this 指向永远指向当前的MyPromise实例
,防止随着函数执行环境的改变而改变
class MyPromise {
// 构造方法
constructor(executor) {
// 初始化值
this.initValue()
// 初始化this指向
this.initBind()
// 执行传进来的函数
// 立即执行函数 将this.resolve和this.reject作为变量传递给resolve和reject
executor(this.resolve, this.reject)
}
initBind() {
// 初始化this
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
initValue() {
// 初始化值
this.PromiseResult = null
this.PromiseState = 'pending'
}
resolve(value) {
// 如果执行resolve方法且状态为pending,则改变状态为fulfilled
if(this.PromiseState !== 'pending') return
this.PromiseState = 'fulfilled'
this.PromiseResult = value
}
reject(reason) {
// 如果执行reject方法且状态为pending,则改变状态为rejected
if(this.PromiseState !== 'pending') return
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
}
测试代码
const test1 = new MyPromise((resolve,reject) => {
resolve('成功')
})
console.log(test1) // MyPromise {PromiseResult: '成功', PromiseState: 'fulfilled', resolve: ƒ, reject: ƒ}
const test2 = new MyPromise((resolve,reject) =>{
reject('失败')
})
console.log(test2) // MyPromise {PromiseResult: '失败', PromiseState: 'rejected', resolve: ƒ, reject: ƒ}
注意,Promise 中有throw
的时候会执行 reject,所以在执行传进来的函数时要使用try catch
+ try {
// 执行传进来的函数
executor(this.resolve, this.reject)
+ } catch (e) {
//捕捉到错误执行reject
+ this.reject(e)
+ }
2. then 的实现
Promise 的 then 有以下功能:
- then 接收两个回调,成功和失败
- 当 Promise 状态为 fulfilled 是执行成功回调,为 rejected 时执行失败回调
- 如 resolve 或 rejected 在定时器里,则定时器结束后再执行 then
- then 支持链式调用,下一次 then 可以接收上一次 then 的返回值
// 类中新增then方法
then(onFulfilled, onRejected) {
// 接收两个回调 onFulfilled onRejected
//校验参数是否为函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected= typeof onRejected=== 'function' ? onFulfilled : reason => { throw reason }
if(this.PromiseState === 'fulfilled') {
onFulFilled(this.PromiseResult)
}else {
onRejected(this.PromiseResult)
}
}
then 的功能已经实现了,但定时器还不行,如果在定时器里执行 resolve 或 reject,then 调用的时候状态还没变更,所以会返回 null,所以我们需要判断状态为 pending 的时候将 then 里的回调保存起来,等定时器里的 resolve 或 reject 执行后再去判断状态,并且判断要执行刚才保存的哪一个回调。所以我们需要一个数组来保存着两个回调。
initValue() {
// 初始化值
this.PromiseResult = null
this.PromiseState = 'pending'
+ this.onFulfilledCallbacks = [] // 保存成功回调
+ this.onRejectedCallbacks = [] // 保存失败回调
}
resolve(value) {
// 如果执行resolve方法且状态为pending,则改变状态为fulfilled
if(this.PromiseState !== 'pending') return
this.PromiseState = 'fulfilled'
this.PromiseResult = value
+ // 执行保存的成功回调
while(this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject(reason) {
// 如果执行reject方法且状态为pending,则改变状态为rejected
if(this.PromiseState !== 'pending') return
this.PromiseState = 'rejected'
this.PromiseResult = reason
+ // 执行保存的失败回调
while(this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult)
}
}
then(onFulfilled, onRejected) {
// 接收两个回调 onFulfilled onRejected
//校验参数是否为函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected= typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if(this.PromiseState === 'fulfilled') {
onFulFilled(this.PromiseResult)
}else if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult)
+ }else if(this.PromiseState === 'pending') {
+ // 如果状态为pending,则保存回调函数
+ this.onFulfilledCallbacks.push(onFulfilled)
+ this.onRejectedCallbacks.push(onRejected)
}
}
现在测试一下定时器的效果:
const testA = new MyPromise((resolve,reject) => {
setTimeout(() => {resolve('成功')},1000)
}).then(res => console.log(res))
结果显示成功,接下来实现 then 链式调用的功能。
Promise 的链式调用有以下几个特点:
- then 方法本身会返回一个 Promise 对象
- 如果返回值是 promise 对象且返回值为成功,新的 promise 就是成功,反之是失败,新的 promise 就是失败
- 如果返回值不是 promise 对象,新的 promise 对象就是成功,值为此返回值
所以我们需要在 then 执行后再返回一个 Promise 对象:
then(onFulfilled, onRejected) {
// 接收两个回调 onFulfilled, onRejected
// 参数校验,确保一定是函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason
}
var thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = cb => {
console.log(cb)
try {
const x = cb(this.PromiseResult)
if (x === thenPromise) {
// 不能返回自身哦
reject('不能返回自身。。。')
}
if (x instanceof MyPromise) {
// 如果返回值是Promise
// 如果返回值是promise对象,返回值为成功,新promise就是成功
// 如果返回值是promise对象,返回值为失败,新promise就是失败
// 谁知道返回的promise是失败成功?只有then知道
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
}
}
if (this.PromiseState === 'fulfilled') {
// 如果当前为成功状态,执行第一个回调
resolvePromise(onFulfilled)
} else if (this.PromiseState === 'rejected') {
// 如果当前为失败状态,执行第二个回调
resolvePromise(onRejected)
} else if (this.PromiseState === 'pending') {
// 如果状态为待定状态,暂时保存两个回调
// 如果状态为待定状态,暂时保存两个回调
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
}
})
// 返回这个包装的Promise
return thenPromise
}
3. async 和 await
1.async
async 关键字用于声明异步函数,可以用在函数声明、函数表达式、箭头函数和方法上。
async function () {}
let test = async function () {}
let test2 = async () => {}
class Qux {
async qux() {}
}
使用 async 关键字可以让函数具有异步特征,但总体上仍然是同步求值的。
async function foo() {
console.log(1)
}
foo()
console.log(2)
// 会按照顺序输出 1 2
但是,如果使用 return 关键字返回了值,就会被 Promise.resolve()包装成一个 promise 对象,在外部调用这个函数可以得到返回值。
async function foo() {
console.log(1)
return 3
}
// 返回的promise调用then方法
foo().then(console.log)
console.log(2)
// 1 => 2 => 3
// 先执行主线程任务输出1 then里是异步任务 进入微任务队列,继续执行主线程任务输出2 ,然后清空微任务队列输出3
2. await
异步函数针对不会马上完成的任务,所以需要一种暂停和恢复执行的能力。使用await
可以暂停异步函数代码的执行,等待期约解决。
let p = new Promise(resolve => setTimeout(resolve, 1000 ,3))
p.then(x => console.log(3))
// 使用async await
async function foo() {
let p = new Promise(resolve => setTimeout(resolve, 1000 ,3))
console.log(await p)
}
foo()
使用 await 后悔暂停执行异步函数后面的代码,染出 JavaScript 运行时的执行线程。
3. await 的限制
await 必须在异步函数中使用,不能再顶级上下文如<script>
标签或模块中使用,但是可以定义并立即调用异步函数。
await 会记录在哪里暂停执行,等到 await 右边的值可用了,JavaScript 会向消息队列推送给一个任务,这个任务会恢复异步函数的执行。因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。
async function foo() {
console.log(2)
await null
console.log(4)
}
console.log(1)
foo()
console.log(3)
上面代码的执行顺序是:
- 打印 1
- 调用异步函数 foo
- 在 foo()中打印 2
- 在 foo 中遇到 await 关键字暂停执行,为立即可用的值 null 向消息队列推送一个任务
- foo()退出
- 打印 3
- 同步线程的代码执行完毕
- 从消息队列中取出任务,恢复异步函数执行
- 在 foo()中恢复执行,await 取得 null 值(并没有使用)
- 继续在 foo 中打印 4
- foo 返回
如果在 await 后是一个期约,为了执行异步函数,则会推送两个任务到消息队列并被异步求值
async function foo() {
console.log(2)
console.log(await Promise.resolve(8))
console.log(9)
}
async function bar() {
console.log(4)
console.log(await 6)
console.log(7)
}
console.log(1)
foo()
console.log(3)
bar()
console.log(5)
运行顺序为:
- 打印 1
- 调用异步函数 foo
- 在 foo 中打印 2
- 在 foo 中遇到 await 暂停,想消息队列添加一个期约在落定之后执行的任务
- 期约立即落定,把给 await 提供值得任务添加到消息队列
- foo 退出
- 打印 3
- 调用异步函数 bar
- 在 bar 中打印 4
- 在 bar 中遇到 await 暂停,为立即可用的值 6 想消息队列中添加一个任务
- bar 退出
- 打印 5
- 顶级线程执行完毕
- JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给他
- 向消息队列添加一个恢复执行 foo 的任务
- 从消息队列中取出恢复执行 bar 的任务及值 6
- bar 恢复执行,await 取得值 6
- bar 中打印 6
- bar 中打印 7
- bar 返回
- 异步任务完成,从消息队列取出恢复执行 foo 的任务及值 8
- foo 中打印 8
- foo 中打印 9
- foo 返回
注:以上步骤为早起规范,await 后的 promise 会生成两个任务,现在新版本浏览器只会生成一个异步任务,所以输出结果为 1 2 3 4 5 8 9 6 7。