阅读 217

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 的执行过程:

  1. 初始化 Promise 状态(Pending)

  2. 立即执行 Promise 中传入的 fn 函数,将 Promise 内部 resolve、reject 函数作为参数传给 fn,按事件机制时机处理

  3. 执行 then(...)注册回调处理数组

  4. 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 的初始状态是pendingresolve和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. 打印 1

  2. 调用异步函数 foo

  3. 在 foo()中打印 2

  4. 在 foo 中遇到 await 关键字暂停执行,为立即可用的值 null 向消息队列推送一个任务

  5. foo()退出

  6. 打印 3

  7. 同步线程的代码执行完毕

  8. 从消息队列中取出任务,恢复异步函数执行

  9. 在 foo()中恢复执行,await 取得 null 值(并没有使用)

  10. 继续在 foo 中打印 4

  11. 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. 打印 1

  2. 调用异步函数 foo

  3. 在 foo 中打印 2

  4. 在 foo 中遇到 await 暂停,想消息队列添加一个期约在落定之后执行的任务

  5. 期约立即落定,把给 await 提供值得任务添加到消息队列

  6. foo 退出

  7. 打印 3

  8. 调用异步函数 bar

  9. 在 bar 中打印 4

  10. 在 bar 中遇到 await 暂停,为立即可用的值 6 想消息队列中添加一个任务

  11. bar 退出

  12. 打印 5

  13. 顶级线程执行完毕

  14. JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给他

  15. 向消息队列添加一个恢复执行 foo 的任务

  16. 从消息队列中取出恢复执行 bar 的任务及值 6

  17. bar 恢复执行,await 取得值 6

  18. bar 中打印 6

  19. bar 中打印 7

  20. bar 返回

  21. 异步任务完成,从消息队列取出恢复执行 foo 的任务及值 8

  22. foo 中打印 8

  23. foo 中打印 9

  24. foo 返回


作者:澶渊
链接:https://juejin.cn/post/7045141697268809758

玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐