JavaScript基本功修练:Day26 - Promise的语法糖:async/await

除了Promise之外,还有async/await语法来处理非同步程序,它背後的操作原理与Promise是一样的,所以也被称为Promise的语法糖,它可以把Promise写得更直觉和简洁(但其实也是见人见智)。

这篇文篇会整理以下知识:

  • async函式、await
  • 比较Promiseasync/await写法
  • 错误处理(then/catch或try...catch方法)
  • async/await抓远端资料

Async函式

async要写在function前面:

async function func() {
    return 10;
}

func() //Promise {<fulfilled>: 10}

async函式一定会回传一个Promise物件,即使例子中的func不是一个Promise物件,它也会被包装成Promise物件。

Await

await一定要写在async函式里,否则会报错。所以写await时,就要一并写async

await工作还未完成,就不会跑後面的程序码

async函式里,一定会等await的工作完成,才继续跑该await後面的程序。这样的写法使程序码看起来更像同步执行(同步即是「做完一件事才接着做下一件事」)。

第一个例子:

async function func(){
    const p = await new Promise( (resolve,reject) => 
    window.setTimeout( () => resolve(100),1000) )
    console.log('跑完p才跑我') //跑完p才跑我
    console.log(p) //100
}

func()

以上例子可见,在async函里const p = ...之後的程序码,需要等const p = ...跑完才会被执行。

第二个例子:
setTimeout延迟计算,完成以下的事(要做完1才可做2)。

  1. 把两个数相加,显示结果
  2. 再把另外两个数相加,显示结果
//把num1和num2相加
function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            resolve( console.log(num1 + num2) );
        },1000)
    })
}

//以下看起来像同步执行的感觉
async function func(){
    //p1跑完,才跑p2
    const p1 = await add(10,20); //30
    const p2 = await add(30,40); //70
}

func()

比较Promise与async/await写法

当我们要执行多次非同步程序时,async/await的写法会更易阅读。以下例子是先把数字1放到func里乘以10,之後把每次回传结果乘以10。我们用此例子来模拟执行多次非同步程序时的情况。

如果用async/await去写:

function func(num){
    return new Promise( (resolve, reject) => {
        resolve(num * 10)
    })
}

async function results(){
    const result1 = await func(1);
    console.log(result1); //10
    const result2 = await func(result1);
    console.log(result2); //100
    const result3 = await func(result2);
    console.log(result3); //1000
}

results()

如果用Promise去写:

function func(num){
    return new Promise( (resolve, reject) => {
        resolve( num * 10)
    })
}

func(1)
    .then( success => {
        console.log(success); //10
        return(func(success));
    })
    .then( success => {
        console.log(success); //100
        return(func(success));
    })
    .then( success => {
        console.log(success); //1000
    })

比较两种写法,async/await的写法里,因为用了await,让人一看就知道这里会处理一个非同步程序,而且要等它完成才会跑下面的程序。所以async/await的写法会更易阅读。

错误处理(Error Handling)

catch处理错误情况

如果前一个await出错,那麽後面的程序就不会跑了:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在这里改成reject,模拟出错
        },1000)
    })
}

async function func(){
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    return `第一次相加结果:${p1},第二次相加结果:${p2}` //Uncaught (in promise) 30
} 

func()

这时候async函式会报错:Uncaught (in promise) 30

刚才提及过,async本身会回传Promise物件,所以我们可以像平时一样,用thencatch方法来抽取Promise物件里的失败/成功值。这里我们用catch来处理错误情况:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在这里改成reject,模拟出错
        },1000)
    })
}

async function func(){
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    return `第一次相加结果:${p1},第二次相加结果:${p2}`
} 

func()
    .then( success => {
        console.log(`成功!`,success)
    })
    .catch( error => {
        console.log(`错误!`,`最後相加结果:${error}`)  //错误! 最後相加结果:30
    })

try...catch处理错误情况

除了thencatch这个组合,另一个方法是用trycatch的语法去处理。直接在async函式里写就可以了:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在这里改成reject,模拟出错
        },1000)
    })
}

async function func(){
    try{
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    console.log(`第一次相加结果:${p1},第二次相加结果:${p2}`)
    
    } catch(error){
        console.log(`错误!`,`最後相加结果:${error}`) //错误! 最後相加结果:30
    }
} 

func()

try...catch用法

题外话,try...catch是一个针对错误处理的语法,原理跟Promise里的thencatch一样,只要出现错误,就会跳去跑catch里的程序。catch(...)里的err的名称是可以自订的。当try里面的程序出现错误,JavaScript会把错误原因放到catch(...)里的参数中,即是err

try {
  // code...
} catch (err) { // err是try区块里程序出错的原因
  // error handling
}

另外,catch後面的参数可以不写,这样就不会回传错误原因给你。例如以下范例(参考自这里),因为在JSON.parse(JSONdata)出错,所以会执行catch里的程序:

let JSONdata = {'错误JSON格式': 123123};

try{
    //JSONdata的格式不合乎JSON的格式,所以JSON.parse会出错
    let data = JSON.parse(JSONdata);
    console.log(data);
}catch{
    console.log('JSON资料格式错误!') //'JSON资料格式错误!'
}

async/await抓远端资料

最常处理非同步程序就是抓远端资料的情况了。以下例子用async/awaitXMLHttpRequest来抓远端资料,并且用try...catch来处理成功或错误结果:

let getJSON = url => {
    return new Promise( (resolve,reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('get',url);
        xhr.send(null);

        xhr.onload = () => {
            if(xhr.status === 200){
                resolve(JSON.parse(xhr.responseText));
            }else{
                reject(new Error(xhr.statusText));
            }
        }
    })
}

//直接在async函式里,用try...catch处理成功或失败的结果
async function printJSON(url){
    try{
    const data = await getJSON(url);
    console.log(data); //显示回传资料  {results: Array(1), info: {…}}

    }catch(error){
        console.log('错误!',error);
    }
}

printJSON('https://randomuser.me/api/')

总结

  • async/awaitPromise的语法糖,背後运作原理与Promise是一样,作用是把Promise写得更易读
  • async要写在function前面
  • async函式一定会回传一个Promise物件
  • await要放在async函式里面
  • async/await的错误处理方法:
    • catch方法(就如平时写Promise一样)
    • try...catch方法

参考资料

JS 原力觉醒 Day16 - Async / Await:Promise 语法糖
JAVASCRIPT.INFO - Async/await
铁人赛:JavaScript Await 与 Async
告别 JavaScript 的 Promise!迎接 Async/Await 的到来
JAVASCRIPT.INFO - Error handling, "try..catch"


<<:  Day 28: Divide and Conquer

>>:  [第26天]30天搞懂Python-直方图

Day27 [实作] 一对一视讯通话(7): 使用 Docker 封装

首先我们需要有 Docker 环境,如果还没有可以参考 Docker 安装 制作 Dockerfil...

第二十九天:版面组合与重构

金鱼都能懂的网页切版:26、27 版面组合 https://codepen.io/mikeyam/p...

[Day 20] - 『转职工作的Lessons learned』 - GraphQL (Hasura) - 身份级别权限设定

先前的GraphQL(Hasura)-Webhook身份验证有介绍到可以使用Webhook做身份级别...

个人笔记 维修单派工 关联表

开始绘制维修单派工关联表,画错蛮多次。我学习的方式,先在网路上找相关的图参考,思考後进行绘制动作。画...

Day15 - Ruby 字串处理入门

线上 Ruby 编辑器:https://runrb.io/ Ruby String 文件:http...