JavaScript 异步编程

67 阅读8分钟

异步编程方式:回调函数、Promsie、async、await

Js 微任务和宏任务

  1. js是单线程的语言
  2. js代码执行流程:同步执行完 ==》 事件循环。同步的任务都执行完了,才会执行事件循环的内容,进入事件循环:请求、定时器、事件。。。
  3. 事件循环中包含:【微任务、宏任务】

微任务;promise.then (then 里面的代码才是 微任务)

宏任务:setTimout。。

要执行宏任务的流程 先要执行完事件循环中的 微任务

先执行完 再执行 事件循环中的 “宏任务” = “微任务”

要执行宏任务的前提是清空了所有的微任务

流程:同步==》事件循环「微任务和宏任务」 == 〉 微任务执行完再值执行宏任务

setTimout(function(){
    console.log('宏任务1') // 宏任务
})
new Promise((resolve) =>{
    console.log('1 promise 1') // 同步任务
    resolve()
}).then(() =>{
     console.log('微任务1')
}).then(() =>{
    console.log('微任务2')
})
console.log('同步2') // 同步任务
打印顺序 1 promise 1' => 同步2 => 微任务1 => 微任务2 =>宏任务1
// 每隔一秒,打印一个5
for (var i =0;i<5;i++){
     //setTimout为
    setTimeout(function(){
        console.log(i)
    },1000)
}
/*
  任务队列
     setTimeout(function(){
        console.log(5)
    },1000)
    setTimeout(function(){
        console.log(5)
    },1000)
    setTimeout(function(){
        console.log(5)
    },1000)
    setTimeout(function(){
        console.log(5)
    },1000)
    setTimeout(function(){
        console.log(5)
    },1000)
*/

每隔几秒输出几?

// 先打印3,再隔一秒打印3,再隔2秒打印3
for (var i =0;i < 3 ;i++){
    setTimout(function(){
        console.log(i)
    },1000 * i)
}
/*
先执行主线程的代码,再执行事件队列的任务。

    setTimout(function(){
        console.log(3)
    },1000 * 0)
    setTimout(function(){
        console.log(3)
    },1000 * 1)
    setTimout(function(){
        console.log(3)
    },1000 * 2)
*/

Promsie

在Promise出现之前,如果需要进行异步操作,一般都使用回调函数。但是如果回调函数嵌套层数过多,就会造成回调地狱:

Promise的内部原理是什么?它的优缺点是什么

Promise对象,封装了一个异步操作并且还可以获取成功或失败的结果

Promise主要就是解决回调地狱的问题,之前如果异步任务比较多,同时他们之间有相互依赖的关系,就只能使用回调函数处理,这样就容易形成回调地狱,代码的可读性差,可维护性也很差

有三种状态; pending初始状态 fulfilled 成功状态 rejected失败状态

状态改变只会有两种情况:pending -> fulfilled,pending ->rejected 一旦发生,状态就会凝固,不会再变

首先我们无法取消promise ,一旦创建它就会立即执行,不能中途取消,如果不设置回调,promise内部抛出的错误就无法反馈到外面,若当前处于pending状态时,无法得知目前在那个阶段。

原理:构造一个Promise实例,实例需要传递函数的参数,这个函数有两个形参,分别是函数类型,一个是resolve一个是reject。

Promise上还有then方法。这个方法就是来指定状态时的确定操作,resolve是执行第一个函数,reject是执行第二个函数。

Promise的基本用法

创建promise对象

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。

then方法

当Promise执行的内容符合成功条件时,调用resolve函数,失败就调用reject函数。那么这里的resolve函数在哪定义的呢?其实你传给then的函数就是resolve函数。

let MyPromise = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(user)
    },1000)
})
MyPromise.then(function (param){
    console.log(param)
})
catch方法

当Promise对象除了有then方法,还有一个catch方法。比如,当我们在Promise里面抛出异常,调用了reject方法,这个reject方法是在哪定义的呢?其实你传给catch的函数就是这个reject方法。

let MyPromise = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        reject("当前出现了网络错误,返回404")
    },1000)
})
MyPromise.then(function (param){
    console.log(param)
}).catch(function (param){
    console.log("调用了catch方法")
    console.log(param)
})
all方法

假设我们需要有3个异步任务(比如3个异步请求),我们希望在3个异步任务全部执行完毕之后,一起把请求结果打印出来,应该如何实现呢?答案是使用all方法。

all方法可以完成并行任务,它接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolve的时候,all方法的状态就会变成resolveed,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected。

let promsie1 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(1)
    },1000)
})
let promsie2 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(2)
    },1000)
})
let promsie3 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(3)
    },1000)
})
// 有一个返回 reject,就会执行catch
Promsie.all([promise1,promise2,promise3]).then((res) =>{
    console.log(res)//结果为:【1,2,3】
}).catch((err) =>{
    console.log("执行了catch")
    console.log(err)
})
race方法

假设有这么一个场景,我们发送了3个请求,我们只需要返回速度最快的请求。比如2号请求最先返回,那么我们直接打印2号的返回结果,不管1号和3号了,那么这种情况下就需要使用race

race方法和all 一样,接受的参数是一个每项都是promsie的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那么自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected

let promsie1 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(1)
    },1000)
})
let promsie2 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(2)
    },2000)
})
let promsie3 = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        resolve(3)
    },3000)
})
// 有一个返回 reject,就会执行catch
Promsie.race([promise1,promise2,promise3]).then((res) =>{
    console.log(res)//结果为:1
}).catch((err) =>{
    console.log("执行了catch")
    console.log(err)
})

race 和 all 的区别 1.Promise.all 接受一个数组作为参数,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。 需要注意,Promise.all获得的成功结果的数组里面的顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据顺序获取和使用数据的场景,就可以使用Promise.all来解决。 2.Promise.race Promise.race就是赛跑的的意思,换而言之,Promise.race([p1,p2,p3])里面哪个结果获得快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事超过多长时间就不做了,就可以用这个方法来解决。

finally方法

finally方法用于指定不管Promsie对象最后状态如何,都会执行的操作

let MyPromise = new Promise(function (resolve,reject){
    setTimeout(function(){
        const user = {id:1,name:"wangjianguo"}
        reject("当前出现了网络错误,返回404")
    },1000)
})
MyPromise.then(function (param){
    console.log(param)
}).catch(function (param){
    console.log("调用了catch方法")
    console.log(param)
}).finally(() =>{
    console.log("执行了finally方法")
})

async、await

async 用于声明一个异步函数,await用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在async函数中,先来看看async函数返回了什么。

async function test(){
    //相当于 Promise.resolve("hello world")
    return "hello world"
}
let result = test()
console.log(result)
result.then((v) =>{
        console.log(v) // hello world
})

async、await的优点

  1. 使用Promise,try/catch难以处理在JSON.parse过程中的问题,原因是这个错误发生在Promsie内部。
const getJSON = () =>{
    const jsonString = "{invalid JSON data}"
    return new Promsie((resolve,reject) =>{
        setTimeout(() =>{
            reject("网络请求发生错误")
        },1000)
    })
}
const makeRequest = () => {
    try {
        getJSON().then((result) =>{
            const data = JSON.parse(result)
            console.log(data)
        }).catch (err) {
            console.log("第一个catch")
            console.log(err)
        }
    } catch (err) {
        console.log("第二个catch")
        console.log(err)
    }
}

const makeRequest2 = () => {
    try {
    // async、await 在try catch 中更容易捕获错误
      const data = JSON.parse(await getJSON())
      console.log(data)
    } catch (err) {
        console.log("第二个catch")
        console.log(err)
    }
}

makeRequest()
  1. 在条件判断中减少嵌套层数
// Promise 用法
const makeRequest = () => {
   retrun getJSON().then((data) =>{
       if (data.needdsAnotherRequest) {
           return makeAnotherRequest(data).then((moreData) =>{
                console.log(moreData)
                return moreData
           })
       }else {
          console.log(data)
          return data
       }
   })
}
// async、await用法
const makeRequest = () => {
   const data = await getJSON()
   if (data.needdsAnotherRequest) {
       const moreData = await makeAnotherRequest()
       console.log(moreData)
       return moreData
   }else {
      console.log(data)
      return data
   }
}

promise 和 async await的区别

  1. 都是处理请求的方式,
  2. promise是ES6的语法,async await 是ES7的语法
  3. async await 是基于promise实现的,他和promise都是非阻塞性的

优缺点:

  1. promise  是返回对象我们要用then,catch方法去处理和捕获异常,并且书写方式是链式,容易造成代码重叠,不好维护,async await 是通过try catch 进行捕获异常

  2. async await 最大的优点就是能让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后再执行后面的操作

    promise.then()的方式返回,就执行了后面的操作。