提问 为什么会出现async与await?
如果你有一个这样的场景,b依赖于a,c依赖于b,那么我们只能通过promise then的方式实现。这样的的可读性就会变得很差,而且不利于流程控制,比如我想在某个条件下只走到 b 就不往下执行 c 了,这种时候就变得不是很好控制!
<script>
Promise.resolve(data)
.then(data1 => {
// do something
})
.then(data2 => {
// do something
}).catch(err => {
console.log(err)
})
// async/await实现上述代码,可读性跟流程控制都变的很方便,但是异常捕获只能通过try/catch来实现
async () => {
try {
const data1 = await Promise.resolve(data);
// do something
const resB = await Promise.resolve(data1);
// do something
} catch (err) {
console.log(err)
}
}
</script>
复制代码
async 和 await 是 ES7 新增两个关键字,它们借鉴了 ES6 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
1.async
目的是简化在函数的返回值中对Promise的创建,async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
<script>
async function test1() {
console.log(1);
return 2;
}
// 等价于
function test1() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
})
}
</script>
复制代码
2.await
await关键字必须出现在async函数中!!!await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。
<script>
async function test1() {
console.log(1);
return 2;
}
async function test2() {
const result = await test1();
console.log(result);
}
//等效于
function test1() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
})
}
function test2() {
return new Promise((resolve, reject) => {
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
// await 后不是promise
async function test2() {
const result = await 1;
console.log(result);
}
//等效于
async function test2() {
const result = await Promise.resolve(1);
console.log(result);
}
// 捕获错误
async function test2() {
try {
const result = await test1();
console.log(result);
} catch (err) {
console.log(err)
}
}
</script>
复制代码
3.用async/await处理一开始的问题:
<script>
// async/await实现上述代码,可读性跟流程控制都变的很方便,但是异常捕获只能通过try/catch来实现
async () => {
try {
const data1 = await Promise.resolve(data);
// do something
const resB = await Promise.resolve(data1);
// do something
} catch (err) {
console.log(err)
}
}
</script>
复制代码
注:如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行。且异常捕获只能通过try/catch来实现。
4.生成器(Generator)模拟async与await
生成器 (Generator)
生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象
<script>
function* test() {
console.log("第1次运行")
yield 1;
console.log("第2次运行")
yield 2;
console.log("第3次运行")
// return 10;
}
test();
const generator = test();
console.log(generator)
</script>
复制代码
调用next方法
b. 如何创建生成器?
生成器的创建,必须使用生成器函数(Generator Function)
c. 如何书写一个生成器函数呢(加星号*)?
这是一个生成器函数,该函数一定返回一个生成器
function* method(){
// ...
}
复制代码
<script>
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [6, 7, 8, 9];
function* createIterator(arr) { // 生成器函数
for (const item of arr) {
yield item;
}
}
const iter1 = createIterator(arr1); // 生成器
const iter2 = createIterator(arr2); // 生成器
// console.log(iter1);
// console.log(iter2);
// 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
function* t1() {
yield "a"
yield "b"
}
function* test() {
yield* t1();
// 相当于
// yield "a";
// yield "b";
yield 1;
yield 2;
yield 3;
}
const generator = test();
</script>
复制代码
5. 生成器函数内部是如何执行的?
1).生成器函数内部是为了给生成器的每次迭代提供的数据
2).每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置
3).yield是一个关键字,该关键字只能在生成器函数内部使用,表示“产生”一个迭代数据。
有哪些需要注意的细节?
1). 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中
function* test() {
console.log("第一次运行");
yield 1;
console.log("第二次运行");
yield 2;
console.log("第三次运行");
return 10;
}
const generator = test();
复制代码
2). 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
3). 第一次调用next方法时,传参没有任何意义
function* test() {
console.log("start");
let info = yield 1;
console.log(info);
info = yield 2 + info;
console.log(info);
}
const generator = test();
复制代码
4). 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
6. 生成器的其他API
– return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束
return放在前面
return放在后面
-throw方法:调用该方法,可以在生成器中产生一个错误
7.迭代器:
从一个数据集合中按照一定的顺序,不断取出数据的过程
<script>
const arr = [1, 2, 3, 4, 5];
//迭代数组arr
const iterator = {
i: 0, //当前的数组下标
next() {
var result = {
value: arr[this.i],
done: this.i >= arr.length
}
this.i++;
return result;
}
}
//让迭代器不断的取出下一个数据,直到没有数据为止
let data = iterator.next();
while (!data.done) { //只要没有迭代完成,则取出数据
console.log(data.value)
//进行下一次迭代
data = iterator.next();
}
console.log("迭代完成")
</script>
复制代码
b. 迭代和遍历的区别?
迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完,遍历强调的是要把整个数据依次全部取出
c. 迭代器与迭代器创建函数
-迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
– 迭代器创建函数(iterator creator):一个返回迭代器的函数
<script>
// 一个简单的迭代器创建函数
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [6, 7, 8, 9];
// 迭代器创建函数 iterator creator
function createIterator(arr) {
let i = 0; //当前的数组下标
return {
next() {
var result = {
value: arr[i],
done: i >= arr.length
}
i++;
return result;
}
}
}
// 等效于
// function* createIterator(arr) {
// for (const item of arr) {
// yield item;
// }
// }
const iter1 = createIterator(arr1).next();
const iter2 = createIterator(arr2);
</script>
复制代码
d. 迭代模式(规范):
– 迭代器应该具有得到下一个数据的能力
– 迭代器应该具有判断是否还有后续数据的能力
e. JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:
{ value: 值,done: 是否迭代完成 },则认为该对象是一个迭代器
8. next方法:
用于得到下一个数据
– 返回的对象 value:下一个数据的值 – done:boolean,是否迭代完成
可迭代协议:
ES6规定,如果一个对象具有知名符号属性Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)。
知名符号:知名符号是一些具有特殊含义的共享符号(根据某个符号名称(符号描述)能够得到同一个符号,Symbol.for(“符号名/符号描述”)),通过 Symbol 的静态属性得到,该符号用于定义构造函数的静态成员。
我们试想一下,如果生成器能够自动执行所有的迭代任务的话,是否执行下次迭代由 Promise 来决定,那么我们就可以实现 async/await 了?
9.生成器实现async
<script>
const fn1 = () => new Promise(resolve => setTimeout(() => resolve("data1"), 1000));
const fn2 = () => new Promise(resolve => setTimeout(() => resolve("data2"), 1000));
function runAsync(generatorFunc) {
const generator = generatorFunc();
let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
handleResult();
//对result进行处理
function handleResult() {
if (result.done) {
return; //迭代完成,不处理
}
//迭代没有完成,分为两种情况
//1. 迭代的数据是一个Promise
if (result.value && result.value.then && typeof result.value.then === "function") {
//1. 迭代的数据是一个Promise
//等待Promise完成后,再进行下一次迭代
result.value.then(data => {
result = generator.next(data)
handleResult();
})
} else {
//2. 迭代的数据是其他数据,直接进行下一次迭代
result = generator.next(result.value)
handleResult();
}
}
}
function* test() {
// await被编译成了yield
const data1 = yield fn1();
console.log('data: ', data1);
const data2 = yield fn2()
console.log('data2: ', data2);
}
runAsync(test);
</script>
复制代码
10 总结
1.是一种编写异步代码的新方法。之前异步代码的方案是callback和promise。
2.建立在 promise 的基础上,与promise一样也是非阻塞的。
3.async/await 让异步代码看起来、表现起来更像同步代码。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END