ES6 async 函数

2024-04-10  本文已影响0人  Cherry丶小丸子

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句

async 函数对 Generator 函数的改进,体现在以下四点

(1)内置执行器
(2)更好的语义
(3)更广的适用性
(4)返回值是 Promise

async 函数有多种使用形式
// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};
返回 Promise 对象

async 函数返回一个 Promise 对象
async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

async function f() {
    return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到

async function f() {
    throw new Error('出错了');
}

f().then(
    v => console.log('resolve', v),
    e => console.log('reject', e)
)
//reject Error: 出错了
await 命令

正常情况下,await 命令后面是一个 Promise 对象,返回该 Promise 对象的结果。如果不是 Promise 对象,就直接返回对应的值

async function f() {
    // 等同于
    // return 123;
    return await 123;
}

f().then(v => console.log(v))
// 123

await 命令后面的 Promise 对象如果变为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收到

async function f() {
    await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

注意,上面代码中,await 语句前面没有 return,但是 reject 方法的参数依然传入了 catch 方法的回调函数。这里如果在 await 前面加上 return,效果是一样的

任何一个 await 语句后面的 Promise 对象变为 reject 状态,那么整个 async 函数都会中断执行

async function f() {
    await Promise.reject('出错了');
    await Promise.resolve('hello world'); // 不会执行
}

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个 await 放在 try...catch 结构里面,这样不管这个异步操作是否成功,第二个 await 都会执行

async function f() {
    try {
        await Promise.reject('出错了');
    } catch(e) {

    }
    return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一种方法是 await 后面的 Promise 对象再跟一个 catch 方法,处理前面可能出现的错误

async function f() {
    await Promise.reject('出错了').catch(e => console.log(e));
    return await Promise.resolve('hello world');
}

f().then(v => console.log(v))
// 出错了
// hello world
使用注意点

第一点,前面已经说过,await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中

async function myFunction() {
    try {
        await somethingThatReturnsAPromise();
    } catch (err) {
        console.log(err);
    }
}

// 另一种写法

async function myFunction() {
    await somethingThatReturnsAPromise()
    .catch(function (err) {
        console.log(err);
    });
}

第二点,多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系。
这样比较耗时,因为只有 getFoo 完成以后,才会执行 getBar,完全可以让它们同时触发

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

第三点,await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

async function dbFuc(db) {
    let docs = [{}, {}, {}];

    // 报错
    docs.forEach(function (doc) {
        await db.post(doc);
    });
}

上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题

function dbFuc(db) { //这里不需要 async
    let docs = [{}, {}, {}];

    // 可能得到错误结果
    docs.forEach(async function (doc) {
        await db.post(doc);
    });
}

上面代码可能不会正常工作,原因是这时三个 db.post() 操作将是并发执行,也就是同时执行,而不是继发执行。
正确的写法是采用 for 循环

async function dbFuc(db) {
    let docs = [{}, {}, {}];

    for (let doc of docs) {
        await db.post(doc);
    }
}

第四点,async 函数可以保留运行堆栈

const a = () => {
    b().then(() => c());
};

上面代码中,函数 a 内部运行了一个异步任务 b()。当 b() 运行的时候,函数 a() 不会中断,而是继续执行。
等到 b() 运行结束,可能 a() 早就运行结束了,b() 所在的上下文环境已经消失了。
如果 b() 或 c() 报错,错误堆栈将不包括 a()

现在将这个例子改成 async 函数

const a = async () => {
    await b();
    c();
};

上面代码中,b() 运行的时候,a() 是暂停执行,上下文环境都保存着。一旦 b() 或 c() 报错,错误堆栈将包括a ()
上一篇 下一篇

猜你喜欢

热点阅读