JavaScript高级程序设计记录-Promise
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 返回
作者:澶渊
链接:https://juejin.cn/post/7045141697268809758
玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/