“async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。asyncawait关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。”

先来提取一下这段话中的关键字即:async[AsyncFunction](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction)awaitPromise ,异步

要想更好的理解async 和 await 的用法,我们必须从异步和 Promise 开始切入

异步函数

———————————————————————————————–

异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的Promise 返回其结果。但是如果你的代码使用了异步函数,它的语法和结构会更像是标准的同步函数。

Promise

———————————————————————————————–

还是先看 mdn 文档:

“Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。”

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

接下来我们可以看一个例子

function SicBo(){
    return new Promise((resolve,reject) =>{
      console.log("开始摇 ")
      setTimeout(()=>{
        let n = parseInt(Math.random()*6 + 1,10) //1~6
        resolve(n)
      },1000)
    })
  }

这个函数理论上会输出一个区间为 [1,7) 的数字,让我们尝试着调用它。

SicBo() //只会出现:"开始摇 "

这次调用成功了吗?答案当然是没有,我们需要用.then来调用它

//SicBo().then(success,fail) //then 就是然后
SicBo().then(
(n)=>{console.log(' 的点数是'+n)},//成功调用成功函数,失败调用失败函数
(y)=>{console.log(' 炸了')})

.then函数中的参数只不过是一个参数名而已,叫x,y,z什么的名字都是一样的,对应着 SicBo 函数中的 n。由于在这个例子中不存在摇 失败的情况,所以我们先不管它。

调用后1s后得到输出

"开始摇 "
" 的点数是5"

async/await

———————————————————————————————–

接下来我们使用 async 和 await来重写这个例子

如果我们直接声明一个 await函数如下

function SicBo(){
    ...
}
let n = await SicBo()

这时,js就会报错,它会告诉你 await只能放在 async函数里

那么我们就创建一个有async标记的函数,然后把await放在函数里面,就不会报错了

async function Roll(){
    let n = await SicBo()
}

这里出现了async/await的第一个关键知识点即:

await只能放在async函数里面,如果不放在里面,代码会直接报错,不能运行。

小知识:JS之父重写创建的 deno支持 let n = await 函数名()这种写法(JS不支持)

整合一下代码,这就是一个最基本的 async/await的例子,await后面接一个会return一个new Promise的函数,并执行这个函数

function SicBo(){
    return new Promise((resolve,reject) =>{
      console.log("开始摇 ")
      setTimeout(()=>{
        let n = parseInt(Math.random()*6 + 1,10) //1~6
        resolve(n)
      },1000)
    })
  }

async function Roll(){
    let n = await SicBo() //一定要写括号,不写括号就不会执行 new Promise
  console.log(' 的点数是'+n)
}

Roll() //记得要调用函数

在这个代码中还需要注意的是,resolve(n)得到的结果会返回给let n = await SicBo()里面的n。在let n = await SicBo()这行代码中,等号左边和等号右边是分离的。右边是1秒之前执行的,左边赋值时1s之后执行的,等号两边不是在同一时间执行的,左边要等右边执行完再执行。所以Roll函数也是要等1s才执行的,所以Roll函数是异步的,所以function前面必须加一个async,表示它的特殊性,让引擎对它进行特殊处理,如果不标记,则报错。

try catch 捕获错误

现在加入Promise中会失败的情况 把摇 改成猜大小

function SicBo(bet){
    return new Promise((resolve,reject) =>{
      console.log("开始摇 ")
      setTimeout(()=>{
        let n = parseInt(Math.random()*6 + 1,10) //1~6
        if(n>3){
          if(bet === "big"){
            resolve(n) //摇出来是大,你也猜了大
          }else{
            reject(n) //摇出来是大,你猜了小
          }
        }else{
          if(bet === "small"){
            resolve(n)
          }else{
            reject(n)
          }
        }
      },1000)
    })
  }

async function Roll(){
    let n = await SicBo() //一定要写括号,不写括号就不会执行 new Promise
  console.log(' 的点数是'+n)
}

Roll()

运行代码的时候我们会发现只会输出“开始摇 ”,打开控制台可以发现控制台报错。

这个错误的结果就是 的点数,显示Uncaught,所以我们要进行一段try...catch代码。

async function Roll(){
    try{
    let n = await SicBo('big') //报错的话左边那一半就不会执行,每次都买大
    console.log(' 的点数是'+n)
    console.log('你猜中了')

  }
  catch(error){
    console.log(' 的点数是'+error)
    console.log('你输光了')
  }
}

我们在一直买大的情况下,成功了就会输出“你猜中了”,买错了就会输出“你输光了”。

这时机智的你可能会发现,怎么JS里面会有try...catch呢?我这是在写Java? 我为啥要用 await和 try...catch,而不用更美观的then呢?

用 .then实现买“大”

SicBo("大").then(f1,f2)

那么为什么需要await呢?

答:因为要更像是标准的同步函数,让异步代码写成同步的样式,同步的代码会更加的直观,执行顺序会更加明确。

.then 麻烦的地方

代码 SicBo("大").then(f1,f2)中只进行了一次.then,那如果是两次或者是多次呢?

两次 Promise 的结果:

SicBo("大").then(f1,f2).then(f3,f4)

这时候我们不禁要问,f3,f4什么时候执行呢?

SicBo成功了,肯定先执行f1,猜错了就执行f2。如果f2不抛出另外的错误,依然会执行f3,f1不抛出另外的错误,也会执行f3,相反,f1或f2如果抛出错误就都会执行f4。

多重Promise的结果

在多重Promise的情况下,理顺函数的执行顺序是相对麻烦的,所以还是那句话await让异步代码写成同步的样式,同步的代码会更加的直观,执行顺序会更加明确

同时玩两个

在玩过几局之后,你可能已经输疯了,血压急剧飙升,迫切的想马上把本金捞回来,你可能就会上头的想同时参与两场猜大小。这次的规则是两次都猜中了,你就可以获得巨额的回报,但只要两次中有一次买错了,你就血本无归了。

这时,你就会发现await的一个局限性:await只能传入一个Promise

那就先试试用promise.all能不能解决这个问题(先把async functionRoll()注释掉)

Promise.all

Promise.all([promise1, promise2]).then(success1, fail1)

特点

promise1和promise2都成功才会调用success1

我们还是一直买”大”

Promise.all([SicBo('big'), SicBo('big')])
  .then((n)=>{console.log(' 的点数是'+n+'你猜中了')}
  , (error)=>{console.log(' 的点数是'+error+'你又输光了')})

结果如下

通过结果我们可以很清晰的看出只有两次摇 的点数都大于3,才会输出“你猜中了”。反之只要有一次点数小于等于3,就会直接输出“你又输光了”

小结

Promise.all接受一个数组,数组里面的每个函数都会返回一个promise,数组里面的函数都成功了,才会调用成功函数,否则调用失败函数。

用asycn/await实现

现在我们把目光重新聚焦于asycn/await

我们之前说到await 后面永远接着一个promise,那么机智的你可能马上就可以反应过来了即:Promise.all也是一个promise

Promise.all是一个新的promise,这个promise的结果基于接受的数组里面的promise决定的,在摇 的例子中是基于两个SicBo('big')决定的,可以解决多个请求(异步)的问题

既然已经明确了Promise.all 也是一个promise,那我们就可以把它放在asycn函数里面,放在await的后面。

async function Roll(){
    try{
    let n/* 数组 */ = await Promise.all([SicBo('big'), SicBo('big')])
    console.log(' 的点数是'+n)
    console.log('你猜中了')

  }
  catch(error){
    console.log(' 的点数是'+error)
    console.log('你输光了')
  }
}

效果一样,如下

promise.race

Promise.all相对应得就是Promise.race。

Promise.race([promise1, promise2]).then(success1, fail1)

特点

promise1promise2只要有一个成功就会调用success1; promise1promise2只要有一个失败就会调用fail1; 总之,谁第一个成功或失败,就认为是race的成功或失败。

Roll()的返回值

———————————————————————————————–

最后 Roll()的返回值是什么呢? .log一下,把Roll()改成下面的代码

let result = Roll()
console.log(result)

返回的是一个promise,再次说明了,所有async都会返回一个promise

总结

———————————————————————————————–

  1. async/awaitpromise的语法糖,作用是为了让Promise更加完善,让代码看上去更加同步
  2. promise.all和 promise.race是为了解决多个解决(异步的结果)
  3. async就是为了标记 function ,其它没啥用
  4. 所有async都会返回一个promise
  5. await只能放在async函数里面

郑重声明

———————————————————————————————–

例子纯属玩笑,我与赌毒不共戴天!!!

1chigua

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注