0


js中如何顺序执行异步任务

在js中,任务可分为两种,同步任务和异步任务。

(1) 同步任务

又叫 非耗时任务,指的是在主线程排队执行的那些任务
只有前一个任务执行完毕,才能执行后一个任务

(2) 异步任务

又叫 耗时任务,异步任务由JavaScript委托给宿主环境进行执行
当异步任务执行完成后,会通知JavaScript主线程执行异步任务的回调函数
当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务

现有a、b和c三个任务,如果其为同步任务,可以很简单地顺序执行,但如果其为异步任务,该如何顺序执行呢?

一、回调函数

functionthing(thingName, callback){setTimeout(()=>{
        console.log(`执行${thingName}任务`)typeof callback ==='function'&&callback()},1000)}// 执行a任务// 执行b任务// 执行c任务thing('a',()=>{thing('b',()=>{thing('c')})})

优点:简单、方便、实用

缺点:回调函数层层嵌套,不易于阅读和维护,形成回调地狱。

二、promise

1. 使用方式

基本使用

newPromise((resolve, reject)=>{// 执行代码}).then(()=>{// 期约兑现}).catch(()=>{// 期约拒绝})

详细使用戳这里

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#%E7%A4%BA%E4%BE%8B

2. 异步任务顺序执行

functionthing(thingName){returnnewPromise(resolve=>{setTimeout(()=>{
            console.log(`执行${thingName}任务`)resolve()},1000)})}// 执行a任务// 执行b任务// 执行c任务thing('a').then(()=>thing('b')).then(()=>thing('c'))

3. 实现原理

那么如何实现一个promise呢?实现promise之前,先分析下promise的结构

  1. promise存在三个状态,pending 待定, fulfilled 兑现, rejected 拒绝,因此需要(1)定义三个常量 PENDINGFULFILLEDREJECTED对应三种状态(2)定义status表示期约当前状态(3)定义value表示已兑现期约值、定义reason表示已拒绝期约原因
  2. 在调用promise的实例函数时,我们传入了一个执行器,执行器接收两个函数,其作用分别为将待定期约转化为已兑现期约与已拒绝期约。因此需要定义两个函数 resolvereject
  3. 已兑现期约、已拒绝期约处理函数 then已拒绝期约处理函数 catch最终执行函数 finally
  4. 静态函数resolverejectallrace的实现

代码实现基于以下链接调整

https://juejin.cn/post/6945319439772434469#heading-30

3.1 promise简易实现

// MyPromise.jsconstPENDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'classMyPromise{// 期约状态, 初始值为pending
  status =PENDING// 已兑现期约值
  value =null// 已拒绝期约原因
  reason =nullconstructor(executor){executor(this.resolve,this.reject)}// 将待定期约转化为已兑现期约resolve=(value)=>{if(this.status ===PENDING){this.status =FULFILLEDthis.value = value
    }}// 将待定期约转化为已拒绝期约reject=(reason)=>{if(this.status ===PENDING){this.status =REJECTEDthis.reason = reason
    }}then(onFulfilled, onRejected){if(this.status ===FULFILLED){onFulfilled(this.value)}elseif(this.status ===REJECTED){onRejected(this.reason)}}}

module.exports = MyPromise

同目录下新建 test.js 文件用于测试

// test.js// 引入我们的 MyPromise.jsconst MyPromise =require('./MyPromise')const promise1 =newMyPromise((resolve, reject)=>{resolve('resolve')})const promise2 =newMyPromise((resolve, reject)=>{reject('reject')})

promise1.then(value=>{
  console.log('promise1 then', value)},reason=>{
  console.log('promise1 catch', reason)})

promise2.then(value=>{
    console.log('promise2 then', value)},reason=>{
    console.log('promise2 catch', reason)})// 执行结果// promise1 then resolve// promise2 catch reject

3.2 加入异步逻辑

继续测试,发现异步执行resolve函数时,会存在问题。因此,我们需对异步逻辑进行处理。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{setTimeout(()=>resolve('resolve'),0)})

promise.then(value=>{
    console.log('promise then', value)},reason=>{
    console.log('promise catch', reason)})// 期望输出 promise then resolve// 实际无输出

(1) 缓存兑现与拒绝回调

// 存储兑现回调函数
onFulfilledCallback =null// 存储拒绝回调函数
onRejectedCallback =null

(2) then 方法中新增待定期约处理

then(onFulfilled, onRejected){if(this.status ===FULFILLED){onFulfilled(this.value)}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallback = onFulfilled
    this.onRejectedCallback = onRejected
  }}

(3) resolve 与 reject 中调用回调函数

// 将待定期约转化为已兑现期约resolve=(value)=>{if(this.status ===PENDING){this.status =FULFILLEDthis.value = value
      // 兑现回调函数存在则执行this.onFulfilledCallback &&this.onFulfilledCallback(value)}}// 将待定期约转化为已拒绝期约reject=(reason)=>{if(this.status ===PENDING){this.status =REJECTEDthis.reason = reason
      // 拒绝回调函数存在则执行this.onRejectedCallback &&this.onRejectedCallback(reason)}}

使用以下代码再次验证,异步问题解决。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{setTimeout(()=>resolve('resolve'),0)})

promise.then(value=>{
    console.log('promise then', value)},reason=>{
    console.log('promise catch', reason)})// 执行结果: promise then resolve

3.3 实现 then 方法多次调用添加多个处理函数

Promise支持添加多个处理函数,来测试下自定义Promise是否满足。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{setTimeout(()=>{resolve('success')},2000)})

promise.then(value=>{
  console.log(1)
  console.log('resolve', value)})
 
promise.then(value=>{
  console.log(2)
  console.log('resolve', value)})

promise.then(value=>{
  console.log(3)
  console.log('resolve', value)})// 3// resolve success

经测试,自定义Promise并不能添加多个处理函数,继续修改。

(1) 新增兑现与拒绝回调数组

// 存储兑现回调函数数组
onFulfilledCallbacks =[]// 存储拒绝回调函数数组
onRejectedCallbacks =[]

(2) then方法中存储回调

then(onFulfilled, onRejected){if(this.status ===FULFILLED){onFulfilled(this.value)}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected)}}

(3) resolve 与 reject 中循环调用回调函数

// 将待定期约转化为已兑现期约resolve=(value)=>{if(this.status ===PENDING){this.status =FULFILLEDthis.value = value
    // 兑现回调函数存在则执行while(this.onFulfilledCallbacks.length){// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空this.onFulfilledCallbacks.shift()(value)}}}// 将待定期约转化为已拒绝期约reject=(reason)=>{if(this.status ===PENDING){this.status =REJECTEDthis.reason = reason
    // 拒绝回调函数存在则执行while(this.onRejectedCallbacks.length){// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空this.onRejectedCallbacks.shift()(reason)}}}

再次测试,问题解决。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{setTimeout(()=>{resolve('success')},2000)})

promise.then(value=>{
  console.log(1)
  console.log('resolve', value)})
 
promise.then(value=>{
  console.log(2)
  console.log('resolve', value)})

promise.then(value=>{
  console.log(3)
  console.log('resolve', value)})// 1// resolve success// 2// resolve success// 3// resolve success

3.4 实现then方法的链式调用

promise是支持链式调用的,我们用自定义的promise来测试下,看是否满足。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{// 目前这里只处理同步的问题resolve('success')})functionother(){returnnewMyPromise((resolve, reject)=>{resolve('other')})}
promise.then(value=>{
  console.log(1)
  console.log('resolve', value)returnother()}).then(value=>{
  console.log(2)
  console.log('resolve', value)// TypeError: Cannot read property 'then' of undefined

可以看到,第一个then函数的返回值为undefined,不能链式调用。继续修改

classMyPromise{......then(onFulfilled, onRejected){const promise2 =newMyPromise((resolve, reject)=>{if(this.status ===FULFILLED){const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(x, resolve, reject)}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected)}})return promise2
  }}functionresolvePromise(x, resolve, reject){// 判断x是不是 MyPromise 实例对象if(x instanceofMyPromise){// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected// x.then(value => resolve(value), reason => reject(reason))// 简化之后
    x.then(resolve, reject)}else{// 普通值resolve(x)}}

测试,完成链式调用。

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{// 目前这里只处理同步的问题resolve('success')})functionother(){returnnewMyPromise((resolve, reject)=>{resolve('other')})}
promise.then(value=>{
  console.log(1)
  console.log('resolve', value)returnother()}).then(value=>{
  console.log(2)
  console.log('resolve', value)})// 1// resolve success// 2// resolve other

3.5 then 方法链式调用识别 Promise 是否返回自己

如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错,原生Promise测试如下

// test.jsconst promise =newPromise((resolve, reject)=>{resolve(100)})const p1 = promise.then(value=>{
    console.log(value)return p1
})// 100// UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>

在 MyPromise 实现一下

// MyPromise.jsclassMyPromise{......then(onFulfilled, onRejected){const promise2 =newMyPromise((resolve, reject)=>{if(this.status ===FULFILLED){const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected)}})return promise2
  }}functionresolvePromise(promise2, x, resolve, reject){// 如果相等了,说明return的是自己,抛出类型错误并返回if(promise2 === x){returnreject(newTypeError('Chaining cycle detected for promise #<Promise>'))}// 判断x是不是 MyPromise 实例对象if(x instanceofMyPromise){// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected// x.then(value => resolve(value), reason => reject(reason))// 简化之后
    x.then(resolve, reject)}else{// 普通值resolve(x)}}
// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{resolve('success')})const p1 = promise.then(value=>{
   console.log('resolve', value)return p1
})

运行一下,结果报错了。从错误提示可以看出,我们必须要等 p1 完成初始化。这里就需要创建一个异步函数去等待 p1 完成初始化,此处使用微任务 -->

queueMicrotask

image.png

修改并执行

// MyPromise.jsclassMyPromise{......then(onFulfilled, onRejected){const promise2 =newMyPromise((resolve, reject)=>{if(this.status ===FULFILLED){queueMicrotask(()=>{const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)})}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected)}})return promise2
  }}
// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{resolve('success')})const p1 = promise.then(value=>{
   console.log('resolve1', value)return p1
})// 运行的时候会走reject
p1.then(value=>{
  console.log('resolve2', value)},reason=>{
  console.log('reject')
  console.log(reason.message)})// 执行结果// resolve1 success// reject// Chaining cycle detected for promise #<Promise>

3.6 错误处理

(1) 捕获执行器错误

// MyPromise.jsclassMyPromise{......constructor(executor){try{executor(this.resolve,this.reject)}catch(error){// 如果有错误,就直接执行 rejectthis.reject(error)}}}

测试一下

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{// resolve('success')thrownewError('执行器错误')})// 2// 执行器错误
promise.then(value=>{
  console.log(1)
  console.log('resolve', value)},reason=>{
  console.log(2)
  console.log(reason.message)})

测试通过。

(2) then 执行的时错误捕获

// MyPromise.jsclassMyPromise{......then(onFulfilled, onRejected){const promise2 =newMyPromise((resolve, reject)=>{if(this.status ===FULFILLED){try{queueMicrotask(()=>{const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)})}catch(error){reject(error)}}elseif(this.status ===REJECTED){onRejected(this.reason)}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(onFulfilled)this.onRejectedCallbacks.push(onRejected)}})return promise2
  }}

测试一下

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{resolve('success')})//  1//  resolve success//  4//  then error
promise.then(value=>{
  console.log(1)
  console.log('resolve', value)thrownewError('then error')},reason=>{
  console.log(2)
  console.log(reason.message)}).then(value=>{
  console.log(3)
  console.log(value)},reason=>{
  console.log(4)
  console.log(reason.message)})

测试通过。

3.7 rejected及pending状态改造

  1. 增加异步状态下的链式调用
  2. 增加回调函数执行结果的判断
  3. 增加识别 Promise 是否返回自己
  4. 增加错误捕获
// MyPromise.jsclassMyPromise{......then(onFulfilled, onRejected){const promise2 =newMyPromise((resolve, reject)=>{constfulfilledMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 获取成功回调函数的执行结果const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}constrejectedMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 调用失败回调,并且把原因返回const x =onRejected(this.reason)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}if(this.status ===FULFILLED){fulfilledMicrotask()}elseif(this.status ===REJECTED){rejectedMicrotask()}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(fulfilledMicrotask)this.onRejectedCallbacks.push(rejectedMicrotask)}})return promise2
  }}

3.8 then 中的参数变为可选

上面我们处理 then 方法的时候都是默认传入 onFulfilled、onRejected 两个回调函数,但是实际上原生 Promise 是可以选择参数的单传或者不传,都不会影响执行。

例如下面这种 👇

// test.jsconst promise =newPromise((resolve, reject)=>{resolve(100)})

promise
  .then().then().then().then(value=> console.log(value))// 输出 100

所以我们需要对 then 方法做一点小小的调整

// MyPromise.jsclassMyPromise{......then(onFulfilled, onRejected){// 如果不传,就使用默认函数
    onFulfilled =typeof onFulfilled ==='function'?onFulfilled:value=> value
    onRejected =typeof onRejected ==='function'?onRejected:reason=>{throw reason }const promise2 =newMyPromise((resolve, reject)=>{constfulfilledMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 获取成功回调函数的执行结果const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}constrejectedMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 调用失败回调,并且把原因返回const x =onRejected(this.reason)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}if(this.status ===FULFILLED){fulfilledMicrotask()}elseif(this.status ===REJECTED){rejectedMicrotask()}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(fulfilledMicrotask)this.onRejectedCallbacks.push(rejectedMicrotask)}})return promise2
  }}

改造完自然是需要验证一下的

先看情况一:resolve 之后

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{resolve('succ')})
 
promise.then().then().then(value=> console.log(value))// 打印 succ

再看情况二:reject 之后

// test.jsconst MyPromise =require('./MyPromise')const promise =newMyPromise((resolve, reject)=>{reject('err')})
 
promise.then().then().then(value=> console.log(value),reason=> console.log(reason))// 打印 err

3.9 catch、finally函数实现

(1) catch 其实是个语法糖,可以通过then函数传入第二个参数实现

(2) finally 的特点如下

无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:
a. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;
b. 否则,新返回的promise会沿用旧promise的决议值进行决议。

// MyPromise.jsclassMyPromise{......catch(onRejected){returnthis.then(null, onRejected)}// 无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:// 1. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;// 2. 否则,新返回的promise会沿用旧promise的决议值进行决议。finally(callback){returnthis.then((data)=>{callback()return data
      },(error)=>{callback()throw error
      })}}

3.10 静态函数实现

(1) resolve

staticresolve(parameter){// 如果传入 MyPromise 就直接返回if(parameter instanceofMyPromise){return parameter
    }returnnewMyPromise(resolve=>{resolve(parameter)})}

(2) reject

// MyPromise.jsclassMyPromise{......staticreject(reason){returnnewMyPromise((resolve, reject)=>{reject(reason)})}}

(3) all

// MyPromise.jsclassMyPromise{......staticall(promiseArray){let result =[]let success =0returnnewPromise((resolve, reject)=>{
      promiseArray.forEach((promise, i)=>{
        MyPromise.resolve(promise).then(res=>{
            result[i]= res
            success++// 全部成功if(success === promiseArray.length){resolve(result)}},err=>reject(err))})})}}

(4) race

// MyPromise.jsclassMyPromise{......staticrace(promiseArray){returnnewPromise((resolve, reject)=>{
      promiseArray.forEach((promise)=>{
        MyPromise.resolve(promise).then(res=>{resolve(res)},err=>reject(err))})})}}

验证一下,木有问题

// test.jsconst MyPromise =require('./MyPromise')// resolve success
MyPromise
  .resolve('success').then((res)=>{
    console.log('resolve', res)})// reject fail
MyPromise
  .reject('fail').then(()=>{},err=>{
    console.log('reject', err)})// all [ 'a', 'b', 'c' ]
MyPromise
  .all([thing('a',1000),thing('b',2000),thing('c',3000)]).then(res=> console.log('all', res))// race a
MyPromise
  .race([thing('a',1000),thing('b',2000),thing('c',3000)]).then(res=> console.log('race', res))functionthing(thingName, timeout){returnnewMyPromise(resolve=>setTimeout(()=>{resolve(thingName)}, timeout))}

3.11 最终代码

// MyPromise.jsconstPENDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'classMyPromise{// 期约状态, 初始值为pending
  status =PENDING// 已兑现期约值
  value =null// 已拒绝期约原因
  reason =null// 存储兑现回调函数数组
  onFulfilledCallbacks =[]// 存储拒绝回调函数数组
  onRejectedCallbacks =[]constructor(executor){try{executor(this.resolve,this.reject)}catch(error){// 如果有错误,就直接执行 rejectthis.reject(error)}}// resolve 静态方法staticresolve(parameter){// 如果传入 MyPromise 就直接返回if(parameter instanceofMyPromise){return parameter
    }returnnewMyPromise(resolve=>{resolve(parameter)})}// reject 静态方法staticreject(reason){returnnewMyPromise((resolve, reject)=>{reject(reason)})}staticall(promiseArray){let result =[]let success =0returnnewPromise((resolve, reject)=>{
      promiseArray.forEach((promise, i)=>{
        MyPromise.resolve(promise).then(res=>{
            result[i]= res
            success++// 全部成功if(success === promiseArray.length){resolve(result)}},err=>reject(err))})})}staticrace(promiseArray){returnnewPromise((resolve, reject)=>{
      promiseArray.forEach((promise)=>{
        MyPromise.resolve(promise).then(res=>{resolve(res)},err=>reject(err))})})}// 将待定期约转化为已兑现期约resolve=(value)=>{if(this.status ===PENDING){this.status =FULFILLEDthis.value = value
      // 兑现回调函数存在则执行while(this.onFulfilledCallbacks.length){// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空this.onFulfilledCallbacks.shift()(value)}}}// 将待定期约转化为已拒绝期约reject=(reason)=>{if(this.status ===PENDING){this.status =REJECTEDthis.reason = reason
      // 拒绝回调函数存在则执行while(this.onRejectedCallbacks.length){// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空this.onRejectedCallbacks.shift()(reason)}}}then(onFulfilled, onRejected){// 如果不传,就使用默认函数
    onFulfilled =typeof onFulfilled ==='function'?onFulfilled:value=> value
    onRejected =typeof onRejected ==='function'?onRejected:reason=>{throw reason }const promise2 =newMyPromise((resolve, reject)=>{constfulfilledMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 获取成功回调函数的执行结果const x =onFulfilled(this.value)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}constrejectedMicrotask=()=>{// 创建一个微任务等待 promise2 完成初始化queueMicrotask(()=>{try{// 调用失败回调,并且把原因返回const x =onRejected(this.reason)// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject)}catch(error){reject(error)}})}if(this.status ===FULFILLED){fulfilledMicrotask()}elseif(this.status ===REJECTED){rejectedMicrotask()}elseif(this.status ===PENDING){this.onFulfilledCallbacks.push(fulfilledMicrotask)this.onRejectedCallbacks.push(rejectedMicrotask)}})return promise2
  }catch(onRejected){returnthis.then(undefined, onRejected)}finally(finalFunc){returnthis.then(finalFunc, finalFunc)}}functionresolvePromise(promise2, x, resolve, reject){// 如果相等了,说明return的是自己,抛出类型错误并返回if(promise2 === x){returnreject(newTypeError('Chaining cycle detected for promise #<Promise>'))}// 判断x是不是 MyPromise 实例对象if(x instanceofMyPromise){// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected// x.then(value => resolve(value), reason => reject(reason))// 简化之后
    x.then(resolve, reject)}else{// 普通值resolve(x)}}

module.exports = MyPromise

4. Promise A+ 测试

4.1 测试

检验一份手写 Promise 靠不靠谱,通过 Promise A+ 规范自然是基本要求,这里我们可以借助 promises-aplus-tests 来检测我们的代码是否符合规范。

(1) 新建test文件夹

(2) 命令行输入以下代码快速初始化

npm init -y

(3) 安装包

npminstall promises-aplus-tests -D

(4) 将 MyPromise.js 拷贝至test文件夹,并添加 deferred 函数

// MyPromise.js......

MyPromise.deferred=function(){const result ={}
  result.promise =newMyPromise(function(resolve, reject){
    result.resolve = resolve
    result.reject = reject
  })return result
}

module.exports = MyPromise

(5) 配置启动命令

{"name":"test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"promises-aplus-tests MyPromise"},"keywords":[],"author":"","license":"ISC","devDependencies":{"promises-aplus-tests":"^2.1.2"}}

(6) 命令行输入以下代码执行

node run test

4.2 修改

执行结果如图

image.png

image.png

可以看出是2.3.x的问题,翻看了一下 Promise A+ 规范,找到以下信息。

Promise A+ 规范链接:https://promisesaplus.com/#point-59![image.png](https://img-blog.csdnimg.cn/img_convert/a323b069f3aba18e2939de8b3126f0c3.png)

翻译如下

2.3.3 否则如果 x 是一个对象或函数,

​ 2.3.3.1 将x.then赋值给then

​ 2.3.3.2 如果取 x.then 的值时抛出错误 e,则以 e为由拒绝 promise。

​ 2.3.3.3 如果then是一个函数,执行并将其this指向x,第一个入参为resolvePromise,第二个入参为rejectPromise

​ 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行

[[Resolve]](promise, y)

​ 2.3.3.3.2 如果 rejectPromise以r 为参数被调用,则以r为由拒绝promise

​ 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用

​ 2.3.3.3.4 如果then执行时抛出异常

​ 2.3.3.3.4.1 如果resolvePromise 或 rejectPromise 被调用,则忽略它

​ 2.3.3.3.4.2 否则,以e为由拒绝promise

​ 2.3.3.4 如果then不是一个函数,则以x为值兑现promise

2.3.4 如果x不是一个对象或函数,则以x为值兑现promise

代码修改如下

functionresolvePromise(promise, x, resolve, reject){// 如果相等了,说明return的是自己,抛出类型错误并返回if(promise === x){returnreject(newTypeError('The promise and the return value are the same'))}if(typeof x ==='object'||typeof x ==='function'){// x 为 null 直接返回,走后面的逻辑会报错if(x ===null){returnresolve(x)}let then
    try{// 把 x.then 赋值给 then 
      then = x.then
    }catch(error){// 如果取 x.then 的值时抛出错误 error ,则以 error 为据因拒绝 promisereturnreject(error)}// 如果 then 是函数if(typeof then ==='function'){let called =falsetry{then.call(
          x,// this 指向 x// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)y=>{// 如果 resolvePromise 和 rejectPromise 均被调用,// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用// 实现这条需要前面加一个变量 calledif(called)return
            called =trueresolvePromise(promise, y, resolve, reject)},// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promiser=>{if(called)return
            called =truereject(r)})}catch(error){// 如果调用 then 方法抛出了异常 error:// 如果 resolvePromise 或 rejectPromise 已经被调用,直接返回if(called)return// 否则以 error 为据因拒绝 promisereject(error)}}else{// 如果 then 不是函数,以 x 为参数执行 promiseresolve(x)}}else{// 如果 x 不为对象或者函数,以 x 为参数执行 promiseresolve(x)}}

再次执行,完美解决。

image.png

三、generator

generator函数,即生成器函数,是一个状态机,封装了多个内部状态。执行一个generator,会返回一个迭代器对象,通过迭代器对象,可以遍历generator函数内部的每个状态。因此,generator函数可以看做是一个迭代器生成器。

1. 基本使用

(1) generator基本形式

generator 函数是在 function 和函数名之间添加

*

来定义的。yield关键字可以让生成器停止和开始执行,生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字之后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数可通过next()方法来恢复执行。

function*foo(){yield1yield2yield3return4}

(2) 执行foo得到一个迭代器

const iterator =foo()

(3) 迭代器遍历

可以通过常用迭代器遍历方法如for-of来遍历迭代器

// 1// 2// 3for(let item of iterator){
    console.log(item)}

也可以手动进行遍历

// 1// 2// 3let item = iterator.next()while(!item.done){
    console.log(item.value)
    item = iterator.next()}

2. 异步任务顺序执行

functionthing(thingName){returnnewPromise(resolve=>{setTimeout(()=>{
            console.log(`执行${thingName}任务`)resolve()},1000)})}function*generator(){yieldthing('a')yieldthing('b')yieldthing('c')}functiondothings(generator){const thingIterator =generator()let thingItem
    exec()functionexec(){
        thingItem = thingIterator.next()if(!thingItem.done){
            Promise.resolve(thingItem.value).then(exec)}}}// 执行a任务// 执行b任务// 执行c任务dothings(generator)

四、async/await

1. 实现原理

1)async/await 就是 generator 的语法糖,使得异步操作变得更加方便
2)async 函数就是将 generator 函数的星号(*)替换成 async,将 yield 替换成await

asyncfunctionthing(thingName){returnnewPromise(resolve=>{setTimeout(()=>{
            console.log(`执行${thingName}任务`)resolve()},1000)})}// generator 生成器函数functionmyAsync(generator){returnfunction(){const iterator =generator()let item

        returnnewPromise((resolve, reject)=>exec(resolve, reject))functionexec(resolve, reject){
            item = iterator.next()if(!item.done){
               Promise.resolve(item.value).then(()=>exec(resolve, reject))}else{// 返回值处理if(item.value instanceofPromise){
                    item.value.then(resolve, reject)}else{resolve(item.value)}}}}}function*generator(){yieldthing('a')yieldthing('b')yieldthing('c')return'the end'}// 模拟定义一个async函数const myAsyncFunc =myAsync(generator)// 执行a任务// 执行b任务// 执行c任务const result =myAsyncFunc()// 返回为一个promise对象
console.log(result instanceofPromise)// true// the end
result.then(res=> console.log(res))

2. 使用注意事项

1)await 后面的promise对象,当其为一个已拒绝期约时,会阻塞后面代码的执行,举例如下

// the first// UnhandledPromiseRejectionWarning: undefinedasyncfunctiontest(){ 
    console.log('the first')await Promise.reject()
    console.log('the second')}test()

对此,可以使用catch函数或try-catch进行处理

catch函数

// the first// the secondasyncfunctiontest(){ 
    console.log('the first')await Promise.reject().catch(()=>{})
    console.log('the second')}test()

try-catch

// the first// the secondasyncfunctiontest(){ 
    console.log('the first')try{await Promise.reject()}catch(e){}
    console.log('the second')}test()

2)多个await后面的异步操作,如果不存在继发关系,最好让它们同时触发

3)await仅能在async函数中使用

4)async函数返回值为一个promise对象

3. 异步任务顺序执行

a、b、c异步任务顺序执行代码如下:

asyncfunctionthing(thingName){returnnewPromise(resolve=>{setTimeout(()=>{
            console.log(`执行${thingName}任务`)resolve()},1000)})}asyncfunctiondothings(){awaitthing('a')awaitthing('b')awaitthing('c')}// 执行a任务// 执行b任务// 执行c任务dothings()
标签: javascript 前端

本文转载自: https://blog.csdn.net/Java_monkey_/article/details/128247070
版权归原作者 Programing_Monkey 所有, 如有侵权,请联系我们删除。

“js中如何顺序执行异步任务”的评论:

还没有评论