【JavaScript】async await

async 函数

async 函数的返回值
  1. return Promise 实例
async function show() {
    return Promise.resolve("ok");
}
let p1 = show();

console.log("p1", p1); // p1 Promise { <pending> }  ——  同步获取 (获取失败)

setTimeout(() => {
    console.log("p1", p1); // p1 Promise {<fulfilled>: 'ok'}  —— 异步获取 (宏任务)
}, 0);

p1.then(val => {
    console.log("val", val); // val ok  —— 异步获取 (微任务)
});

输出顺序:p1 Promise { <pending> }val okp1 Promise {<fulfilled>: 'ok'}

  1. return 非 Promise 实例,则得到一个 fulfilled 的 Promise 实例,该 Promise 实例的值为 return 的数据
async function show() {
    return 1; // 相当于 return Promise.resolve(数据值)
}
let p1 = show();

console.log("p1", p1); // p1 Promise {<fulfilled>: 1}  ——  同步获取

p1.then(val => {
    console.log("val", val); // val 1  —— 异步获取
});
  1. 抛出异常,得到 rejected 的 Promise 实例,该 Promise 实例的值就是抛出的异常数据
async function show() {
    throw "出错了";
}
let p1 = show();

console.log("p1", p1); // p1 Promise {<rejected>: '出错了'}  ——  同步获取

p1.catch(reason => {
    console.log("reason", reason); // reason 出错了  ——  异步获取
});
async 函数的写法
async function fun1() {} // 普通函数

let fun2 = async function () {}; // 函数表达式

let fun3 = async () => {}; // 箭头函数

let obj = {
    name: "superman",
    async showName() {}, // 对象方法
};



await 关键字

  • 我们可以在 async 函数的直接子作用域中,使用 await 关键字来控制函数的执行

  • await 同一行后面的内容对应 Promise 主体内容,即同步执行的;

    await 下一行的内容对应 then() 里面的内容,是异步执行的

  • 所以,async 函数中,第一个 await 之前的代码会同步执行,之后的代码会异步执行

async function getData() {
    const data = await axios.get(url);
    return data;
}
// 等效于
function getData() {
    return axios.get(url).then(data => {
        return data;
    });
}
  • await 同一行后面应该跟着一个 Promise 对象;如果不是,需要转换(如果是常量会自动转换)

比如下面写法就是不正确的:

async function getData() {
    // await 不认识后面的 setTimeout, 不知道何时返回
    const data = await setTimeout(() => {
        return;
    }, 3000);
    console.log("3 秒到了");
}

正确写法是:

async function getData() {
    const data = await new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, 3000);
    });
    console.log("3 秒到了");
}
  • 如果 await 右侧的数据是非 Promise 实例,则会将其转为 Promise 实例,数据值为该 Promise 实例的值;
    然后将该 Promise 实例的值赋值给左侧的变量
  • 如果 await 的右侧为 Promise 实例时,只要 Promise 实例的状态没改变,函数就会挂起等待;
    若 Promise 实例状态变成 fulfilled,则将该 Promise 实例的值赋给 await 左侧的变量,然后继续往下执行程序
async function test() {
    console.log("async");

    let val1 = await new Promise(resolve => {
        setTimeout(() => {
            resolve("ProResolve");
        }, 1000);
    });
    console.log(val1);

    let val2 = await "普通数据";
    console.log(val2);
}

console.log("before await");
test();
console.log("after await");

上例的输出顺序是 before await - async - after await - ProResolve - 普通数据

  • 上例等效于:
console.log("before await");

new Promise(resolve => {
    console.log("async");
    setTimeout(() => {
        resolve("ProResolve");
    }, 1000);
})
    .then(val1 => {
        console.log(val1);
        return "普通数据";
    })
    .then(val2 => console.log(val2));

console.log("after await");
  • 如果 await 右侧的是 rejected 状态的 Promise 实例,就会抛出异常:
    需要通过 try … catch 语句捕获并处理错误
    也可以使用 Promise 实例调用 catch 方法处理错误
async function show() {
    let res = await Promise.reject("wrong"); // 这里抛出错误,后面的语句都不会执行
    console.log("res", res); // 这里不会打印
}
show();
async function show() {
    let res = await Promise.reject("wrong").catch(reason => {
        console.log("reason", reason); // reason wrong
    });
    console.log("res", res); // res undefined
}
show();
async function show() {
    try {
        let res = await Promise.reject("wrong");
        console.log("res", res); // 这里不会打印
    } catch (error) {
        console.log("error", error); // error wrong
    }
}
show();
  • 如果不用 await 关键字,也不 return Promise 实例,async 函数与普通函数一样
async function test() {
    console.log("async");
    return 1;
}
console.log("before async");
const res = test();
console.log("res", res);
console.log("after async");

上例的输出顺序是 before asyncasyncres Promise {<fulfilled>: 1}after async



await 使用陷阱

  1. 对于并行执行的异步任务,其实并不适合用 await 修饰
async function fun() {
    let pro1 = await fetch("url1");
    let pro2 = await fetch("url2");
    // ...
}

上例这样写,虽没有错误,但会打破两个 fetch 的并行,这需要等待第 1 个异步任务执行完毕之后,才会执行第 2 个异步任务
所以我们可以不用 await 修饰这些并行执行的异步操作

async function fun() {
    let proA = fetch("url1"); // 只要不加 await,Ajax 就会同时请求
    let proB = fetch("url2");
    let pro1 = await proA;
    let pro2 = await proB;
    console.log(pro1, pro2);
    // ...
}

这里更高效的做法:通过 Promise.all 组合起来

async function fun() {
    let proA = fetch("url1"); // 只要不加 await,Ajax 就会同时请求
    let proB = fetch("url2");
    let [pro1, pro2] = await Promise.all([proA, proB]);
    // ...
}
  1. 如果我们需要在循环中执行异步操作,是不能够直接调用 forEach / map 这一类方法的,尽管我们在回调函数中写了 await,但这里的 forEach 会立刻返回,并不会暂停等到所有异步操作都执行完毕
async function fun() {
    [1, 2, 3].forEach(async i => {
        await someAsyncOperation();
    });
    console.log("done");
}
fun();

如果我们希望等待循环中的异步操作都完成之后才继续执行,那我们还是应该使用传统的 for 循环

async function fun() {
    for (let i of [1, 2, 3]) {
        await someAsyncOperation();
    }
    console.log("done");
}
fun();

更进一步,如果我们想所有操作都并发执行,可以使用 for await
这里的 for 循环依然会等到所有的异步操作都完成之后才继续向后执行

async function fun() {
    let pros = [someAsyncOperation(), someAsyncOperation(), someAsyncOperation()];
    for await (let result of pros) {
        // ...
    }
    console.log("done");
}
fun();



案例

使用回调函数读取多个文件

const fs = require('fs');

fs.readFile('./demo.html', (err, data1) => {
    if (err) throw err;
    fs.readFile('./demo.html', (err, data2) => {
        if (err) throw err;
        fs.readFile('./demo.html', (err, data3) => {
            if (err) throw err;
            console.log(data1 + data2 + data3);
        });
    });
});

使用 async & await 读取多个文件

const fs = require('fs');
const util = require('util');

// 把 fs.readFile 方法封装成 Promise 实例 
let myReadFile = util.promisify(fs.readFile);

async function main() {
    try {
        let data1 = await myReadFile('./demo.html');
        let data2 = await myReadFile('./demo.html');
        let data3 = await myReadFile('./demo.html');
        console.log(data1 + data2 + data3);
    } catch (error) {
        console.log(error);
    }
}

main();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值