总结
- 1、async函数的执行,与普通函数一模一样,只要一行,不需要调用next方法
- 2、await后面是原始类型的值会自动转成立即 resolved 的Promise 对象(resolved(原始值本身))
- 3、
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数
- 4、
async
函数返回的Promise
对象,必须等到
内部所有await
命令后面
的Promise
对象执行完
,才会发生状态改变,除非
遇到return
语句或者抛出错误
- 5、任何一个await语句后面的 Promise 对象变为reject状态那么整个
async
函数都会中断执行 - 6、最好把await命令放在try...catch代码块中
- 7、多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发,await Promise.all([getFoo(), getBar()])
含义
1、async
函数就是 Generator
函数的语法糖
async
函数就是将Generator
函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已
使用Generator 函数,依次读取两个文件:
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数gen
可以写成async
函数
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数通过四点
对 Generator 函数进行改进
(1)内置执行器
async函数的执行,与普通函数一模一样,只要一行,不需要调用next方法,或者用co模块
Generator
函数的执行必须靠执行器
,所以才有了co
模块,而async
函数自带执行器
。也就是说,async
函数的执行
,与普通函数一模一样
,只要一行。
不需要
调用next
方法,或者用co
模块
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//async函数执行
asyncReadFile();
(2)更好的语义
async
和await
,比起星号和yield
,语义更清楚了。
async
表示:函数里有异步操作
await
表示:紧跟在后面的表达式需要等待结果
(3)更广的适用性
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象
。
async
函数的await
命令后面,可以是Promise
对象和原始类型的值
(数值、字符串和布尔值,但这时会自动转成立即 resolved 的Promise 对象
)
await
后面是原始类型的值会自动转成立即resolved
的Promise
对象(resolved(原始值本身)
)
(4)返回值是 Promise
async
函数的返回值是 Promise
对象,这比 Generator
函数的返回值是Iterator
对象方便多了。可以用then
方法指定下一步的操作
async
函数完全可以看作多个异步操作
,包装成的一个 Promise
对象,而await
命令就是内部then
命令的语法糖
基本用法
1、async
函数返回
一个 Promise
对象,可以
使用then
方法添加回调函数
2、当函数执行
的时候,一旦遇到await
就会先返回,等
到异步操作完成
,再接着执行
函数体内后面的语句
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result); //就是stockPrice
});
上面代码函数前面的async
关键字,表明
该函数内部有异步操作
。调用
该函数时,会立即
返回一个Promise
对象,所以可以用then
监听函数完成。
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);//5s后输出 'hello world'
//改造一下下。。。
async function asyncPrint(value, ms) {
await timeout(ms);
return value;
}
asyncPrint('hello world', 5000).then((result)=>{
console.log(result);
});//5s后输出 'hello world'
由于async
函数返回的是Promise
对象,可以作为await
命令的参数
所以,上面的例子也可以写成下面的形式:
//将timeout也改为async函数
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', 50);
async 函数有多种使用形式:
函数声明
函数表达式
对象的方法
Class 的方法
箭头函数
// 函数声明
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内部return命令返回的值,会被then方法回调函数接收到。
f().then(v => console.log(v))
// "hello world"
async
函数内部抛出错误,会导致返回的Promise
对象变为reject
状态,抛出的错误对象会被catch方法回调函数接收到
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e,'e')
)
// Error: 出错了,'e'
Promise 对象的状态变化
1、async
函数返回的 Promise
对象,必须等到
内部所有await
命令后面
的 Promise
对象执行完
,才会发生状态改变,除非
遇到return
语句或者抛出错误
2、只有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 命令
1、await
命令后面是一个 Promise
对象,返回该对象的结果
2、如果不是 Promise
对象,就直接返回对应的值
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
3、await命令后面是一个thenable对象(即定义then方法的对象
),那么await
会将其等同于 Promise
对象
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const actualTime = await new Sleep(1000);
console.log(actualTime);
})();
上面代码中,await
命令后面是一个Sleep
对象的实例。这个实例不是 Promise
对象,但是因为定义了then
方法,await
会将其视为Promise
处理。
4、await命令后面的 Promise 对象如果变为reject状态则reject
的参数会被catch
方法的回调函数接收到
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e,'e'))
// 出错了,'e'
上面代码中,await
语句前面没有return
,但是reject
方法的参数依然传入了catch
方法的回调函数。
这里如果在await
前面加上return
,效果是一样的。
async function f() {
return await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e,'e'))
// 出错了,'e'
5、任何一个await语句后面的 Promise 对象变为reject状态那么整个async
函数都会中断执行
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
6、即使前一个异步操作失败,也不要中断后面的异步操作
(1)try...catch
可以将第一个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
(2)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
错误处理
1、如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise
对象被reject
sync function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e,'e'))
// Error:出错了,'e'
2、防止出错的方法,也是将其放在try...catch
代码块之中
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
}
return await('hello world');
}
3、如果有多个await
命令,可以统一放在try...catch
结构中
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
4、使用try...catch
结构,实现多次重复尝试
//如果await操作成功,就会使用break语句退出循环;如果失败,会被catch语句捕捉,然后进入下一轮循环。
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
使用注意点
1、最好把await命令放在try...catch代码块中
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);
});
}
2、多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发,await Promise.all([getFoo(), getBar()])
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;
3、await命令只能用在async函数之中
用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
如果将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);
}
}
多个请求并发执行,可以使用Promise.all方法
当三个请求都会resolved
时,下面两种写法效果相同。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者使用下面的写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
esm
模块加载器支持顶层await
,即await
命令可以不放在async
函数里面,直接使用
// async 函数的写法
const start = async () => {
const res = await fetch('google.com');
return res.text();
};
start().then(console.log);
// 顶层 await 的写法
const res = await fetch('google.com');
console.log(await res.text());
//第二种写法的脚本必须使用esm加载器,才会生效。
4、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()
。
async 函数的实现原理
async
函数的实现原理,就是将 Generator
函数和自动执行器
,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
所有的async
函数都可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
下面给出spawn
函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
与其他异步处理方法的比较
示例:假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。
Promise 的写法
function chainAnimationsPromise(elem, animations) {
// 变量ret用来保存上一个动画的返回值
let ret = null;
// 新建一个空的Promise
let p = Promise.resolve();
// 使用then方法,添加所有动画
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// 返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
/* 忽略错误,继续执行 */
}).then(function() {
return ret;
});
}
Generator 函数的写法
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(let anim of animations) {
ret = yield anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
});
}
这个写法的问题在于,必须有一个任务运行器,自动执行Generator
函数,上面代码的spawn
函数就是自动执行器,它返回一个 Promise
对象,而且必须保证yield
语句后面的表达式,必须返回一个 Promise
。
async 函数的写法
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
实例:按顺序完成异步操作
依次远程读取一组 URL
,然后按照读取的顺序输出结果。
Promise 的写法
function logInOrder(urls) {
// 远程读取所有URL
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});
// 按次序输出
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}
上面代码使用fetch
方法,同时远程读取一组 URL
。每个fetch
操作都返回一个Promise
对象,放入textPromises
数组。然后,reduce
方法依次处理每个 Promise
对象,然后使用then
,将所有 Promise
对象连起来,因此就可以依次输出结果。
async 函数实现并发
注意:只有async
函数内部是继发执行,外部不受影响
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
上面代码问题是:所有远程操作都是继发
。只有前一个 URL
返回结果,才会去读取下一个 URL
,这样做效率很差,非常浪费时间。
我们需要的是并发发出远程请求。
并发发出远程请求:每次都是独立的一个async函数 相互不影响(也不受父级async函数影响)
async function logInOrder(urls) {
// 并发读取远程URL
//每次都是独立的一个async函数 相互不影响(也不受父级async函数影响)
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
//在 父级async函数 中使用 await 按顺序 输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
虽然map
方法的参数是async
函数,但它是并发执行
的,因为只有async
函数内部是继发执行,外部不受影响。后面的for..of
循环内部使用了await,因此实现了按顺序输出。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!