首页 > 其他分享 >js中如何顺序执行异步任务

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

时间:2022-12-09 09:11:51浏览次数:64  
标签:异步 顺序 console value js MyPromise resolve promise reject

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

(1) 同步任务

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

(2) 异步任务

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

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

一、回调函数

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

// 执行a任务
// 执行b任务
// 执行c任务
thing('a', () => {
    thing('b', () => {
        thing('c')
    })
})

优点:简单、方便、实用

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

二、promise

1. 使用方式

基本使用

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

详细使用戳这里

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#示例

2. 异步任务顺序执行

function thing(thingName) {
    return new Promise(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的实例函数时,我们传入了一个执行器,执行器接收两个函数,其作用分别为将待定期约转化为已兑现期约与已拒绝期约。因此需要定义两个函数 resolve reject

  3. 已兑现期约、已拒绝期约处理函数 then

    已拒绝期约处理函数 catch

    最终执行函数 finally

  4. 静态函数 resolverejectall race 的实现

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

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

3.1 promise简易实现

// MyPromise.js

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 期约状态, 初始值为pending
  status = PENDING
  // 已兑现期约值
  value = null
  // 已拒绝期约原因
  reason = null

  constructor(executor){
    executor(this.resolve, this.reject)
  }

  // 将待定期约转化为已兑现期约
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
    }
  }

  // 将待定期约转化为已拒绝期约
  reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }

  then (onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    } else if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

module.exports = MyPromise

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

// test.js

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

const promise2 = new MyPromise((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.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((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)
  } else if (this.status === REJECTED) {
    onRejected(this.reason)
  }  else if (this.status === PENDING) {
    this.onFulfilledCallback = onFulfilled
    this.onRejectedCallback = onRejected
  }
}

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

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

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

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

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((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.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((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)
  } else if (this.status === REJECTED) {
    onRejected(this.reason)
  }  else if (this.status === PENDING) {
    this.onFulfilledCallbacks.push(onFulfilled)
    this.onRejectedCallbacks.push(onRejected)
  }
}

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

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

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

再次测试,问题解决。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((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.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
  
// TypeError: Cannot read property 'then' of undefined

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

class MyPromise {
	......

  then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        const x = onFulfilled(this.value)
        // 传入 resolvePromise 集中处理
        resolvePromise(x, resolve, reject)
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}

function resolvePromise(x, resolve, reject) {
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

测试,完成链式调用。

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
})

// 1
// resolve success
// 2
// resolve other

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

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

// test.js

const promise = new Promise((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.js

class MyPromise {
  ......
  then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        const x = onFulfilled(this.value)
        // 传入 resolvePromise 集中处理
        resolvePromise(promise2, x, resolve, reject)
      } else if (this.status === REJECTED) {
        onRejected(this.reason)
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled)
        this.onRejectedCallbacks.push(onRejected)
      }
    })

    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}
// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    resolve('success')
})
 
const p1 = promise.then(value => {
   console.log('resolve', value)
   return p1
})
 

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

image.png

修改并执行

// MyPromise.js

class MyPromise {
  ......

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

    return promise2
  }
}
// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((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.js

class MyPromise {
  ......

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

测试一下

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    // resolve('success')
    throw new Error('执行器错误')
})

// 2
// 执行器错误
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
}, reason => {
  console.log(2)
  console.log(reason.message)
})

测试通过。

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

// MyPromise.js

class MyPromise {
  ......

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

    return promise2
  }
}

测试一下

// test.js

const MyPromise = require('./MyPromise')

const promise = new MyPromise((resolve, reject) => {
    resolve('success')
 })
 
//  1
//  resolve success
//  4
//  then error
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  throw new Error('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.js

class MyPromise {
  ......

then (onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = onFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })  
      }
  
      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = onRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        }) 
      }

      if (this.status === FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === REJECTED) {
        rejectedMicrotask()
      }  else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      }
    })

    return promise2
  }
}

3.8 then 中的参数变为可选

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

例如下面这种

标签:异步,顺序,console,value,js,MyPromise,resolve,promise,reject
From: https://www.cnblogs.com/orangehome/p/16968024.html

相关文章

  • JavaScript入门⑨-异步编程●异世界之旅
    JavaScript入门系列目录JavaScript入门①-基础知识筑基JavaScript入门②-函数(1)基础{浅出}JavaScript入门③-函数(2)原理{深入}执行上下文JavaScript入门④-万物皆......
  • [未解决] write javaBean error, fastjson version 1.2.76, class org.springframewor
    本地测试正常,打包部署后报错如下:writejavaBeanerror,fastjsonversion1.2.76,classorg.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResp......
  • 使用TWEEN.js时update更新发现初始点位全部变成NaN
    在只用tween时只要执行了update方法就会发现传入的点变为NaN,从而导致模型消失console.log(t.target,t.paras);tw=newTWEEN.Tween(t.paras).to(t.target,d)......
  • 官网下载UEditor文件没有ueditor.all.js、ueditor.all.min.js文件
    需本机已安装node.js安装Grunt执行命令npminstall-ggrunt-cli以安装grunt,等待呈现如下图所示界面就算成功 建立 C:\ueditor\ 目录。在建立需要的版本目录 ,......
  • Threejs:光影
     环境光constcolor=0xFFFFFF;constintensity=1;constlight=newTHREE.AmbientLight(color,intensity);scene.add(light);创建地面constplaneSize=......
  • js 把一个数组赋值给另一个数组
    varaArr=["a","b","c"];varbArr=aArr;这种情况下的赋值无论是对aArr还是bArr做出操作(例如删除数组中的一个元素),都将影响另一个,因为数组是指向原位置的。要切断两......
  • Threejs:捕获鼠标位置
     //获取鼠标坐标functiononPointerMove(event){//将鼠标点击位置的屏幕坐标转换成threejs中的标准坐标mouse.x=(event.clientX/window.innerWidth)*2-......
  • Threejs:创建矩阵
     设置顶点创建矩形constgeometry3=newTHREE.BufferGeometry();constvertices=newFloat32Array([-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0,......
  • Threejs:创建纹理
    创建纹理//导入纹理constloader=newTHREE.TextureLoader();//加载所需要的纹理图片consttexture1=loader.load('./dist/texture/sea.jpg')constmaterial5......
  • Threejs:创建几何体——图元
     BoxGeometry盒子+MeshBasicMaterialconstgeometry=newTHREE.BoxGeometry(1,1,1);constmaterial=newTHREE.MeshBasicMaterial({color:0x00ff00});const......