本文深入解析ES6 Async函数,阐述其作为Generator函数语法糖的优势,包括内置执行器、语义更清晰、适用性更广及返回Promise对象等特性。文章详细讲解基本用法、语法规则、错误处理机制及并发异步操作的最佳实践,帮助开发者高效掌握现代JavaScript异步编程。
本文旨在详细介绍ES6中Async函数的核心内容,供广大开发者参考学习。
Async函数概述
Async函数概述
Async函数的出现极大地简化了异步操作的编写流程,它本质上是Generator函数的语法糖。
为了理解Async函数的优势,我们先看一个使用Generator函数依次读取两个文件的例子:
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
若将其改写为Async函数,代码结构如下:
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
相比Generator函数,Async函数在以下四个方面进行了显著改进:
1)内置执行器
Generator函数的执行依赖于外部执行器(如co模块),而Async函数内置了执行器。这意味着Async函数的调用方式与普通同步函数完全一致,仅需一行代码即可触发执行。
var result = asyncReadFile();
例如,调用asyncReadFile函数后,系统会自动执行并输出最终结果。这与需要手动调用next方法或使用co模块才能运行的Generator函数形成鲜明对比。
2)更好的语义表达
async和await关键字比星号(*)和yield具有更清晰的语义。async明确标识函数内包含异步操作,而await则指明其后的表达式必须等待结果返回。
3)更广的适用场景
co模块规定yield命令后只能跟随Thunk函数或Promise对象;而Async函数的await命令后不仅可以是Promise对象,还可以是原始类型的值(如数值、字符串、布尔值),此时等同于同步操作。
4)返回值类型为Promise
Async函数的返回值始终是一个Promise对象,这比Generator函数返回的Iterator对象更为便捷,开发者可以直接使用then方法指定后续操作。
从本质上看,Async函数可视为将多个异步操作封装为一个Promise对象,而await命令则是内部then调用的语法糖。
基本用法
一、基本用法
Async函数返回一个Promise对象,可通过then方法添加回调函数。函数执行时,一旦遇到await关键字便会先返回,待异步操作完成后,再继续执行函数体剩余的语句。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 5000);
上述代码指定在5000毫秒后输出“hello world”。
由于Async函数返回的是Promise对象,因此它可以作为await命令的参数。基于此特性,上述示例也可改写为以下形式:
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 5000);
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('hzzly').then(…);
// 箭头函数
const foo = async () => {};
语法规范
二、语法规范
Async函数的语法规则总体较为简洁,其难点主要在于错误处理机制。
返回Promise对象
Async函数返回一个Promise对象。函数内部return语句返回的值,将作为then方法回调函数的参数。
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
Promise对象的状态变化
Async函数返回的Promise对象,必须等待内部所有await命令后的Promise对象执行完毕,才会发生状态改变。除非遇到return语句或抛出错误。也就是说,只有当Async函数内部的异步操作全部执行完毕,才会执行then方法指定的回调函数。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
在上面的代码中,函数getTitle内部包含三个操作:抓取网页、提取文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法中的console.log。
使用注意事项
三、使用注意事项
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命令后的异步操作,若它们之间不存在继发关系(即互不依赖),最好让它们同时触发,以提高效率。
//异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
let foo = await getFoo();
let bar = await 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函数的使用示例,可参考相关文章:https://www.jb51.net/article/113475.htm
总结
以上就是本文的全部内容,希望这些内容能对各位的学习或工作有所帮助。如有任何疑问,欢迎留言交流,感谢大家对本站的支持。