提问 为什么会出现async与await?
如果你有一个这样的场景,b依赖于a,c依赖于b,那么我们只能通过promise then的方式实现。这样的的可读性就会变得很差,而且不利于流程控制,比如我想在某个条件下只走到 b 就不往下执行 c 了,这种时候就变得不是很好控制!
<script>
Promise.resolve(data)
.then(data1 => {
})
.then(data2 => {
}).catch(err => {
console.log(err)
})
async () => {
try {
const data1 = await Promise.resolve(data);
const resB = await Promise.resolve(data1);
} 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();
})
})
}
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 () => {
try {
const data1 = await Promise.resolve(data);
const resB = await Promise.resolve(data1);
} 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次运行")
}
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);
function* t1() {
yield "a"
yield "b"
}
function* test() {
yield* t1();
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];
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];
function createIterator(arr) {
let i = 0;
return {
next() {
var result = {
value: arr[i],
done: i >= arr.length
}
i++;
return result;
}
}
}
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();
function handleResult() {
if (result.done) {
return;
}
if (result.value && result.value.then && typeof result.value.then === "function") {
result.value.then(data => {
result = generator.next(data)
handleResult();
})
} else {
result = generator.next(result.value)
handleResult();
}
}
}
function* test() {
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 让异步代码看起来、表现起来更像同步代码。