异步方案
# 异步方案
# 迭代器和生成器
# 迭代器(Iterator)
# 什么是迭代器?
迭代器是帮助我们对某个数据结构进行遍历的对象,迭代器是一个对象。
这个对象需要符合迭代器协议(Iterator Protocol)
- 迭代器协议定义了产生一系列值(无论是有限个还是无限个) 的标准方法。
- 在 js 中这个标准就是一个特定的 next 方法。
next 方法有以下要求:
- 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
- done(boolean 类型):
- 如果迭代器可以产生序列中的下一个值,则为 false(等价于没有指定 done 的这个属性)。
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果 value 依然存在,即为迭代结束之后的默认返回值。
- value:迭代器返回的任何JavaScript 代码,done 为 true 时可以省略。
# 迭代器实现
function createIterator(arr) {
const length = arr.length;
let index = 0;
return {
next() {
return { done: index >= length, value: arr[index++] };
},
};
}
const arr = ['aaa', 'bbb', 'ccc'];
// 这里得到的iterator就是一个迭代器,因为createIterator函数返回的对象满足迭代器协议。
const iterator = createIterator(arr);
console.log(iterator.next()); // { done: false, value: 'aaa' }
console.log(iterator.next()); // { done: false, value: 'bbb' }
console.log(iterator.next()); // { done: false, value: 'ccc' }
console.log(iterator.next()); // { done: true, value: undefined }
# 什么是可迭代对象?
- 当一个对象实现了可迭代协议(iterable protocol) 时,它就是一个可迭代对象。
- 这个对象要求是必须实现@@iterator 方法,在代码中使用Symbol.iterator访问该属性。
- String,Array,Map,Set,arguments 对象,NodeList 集合等都是可迭代对象。可以通过调用 Symbol.iterator 生成一个迭代器对象。
# 可迭代对象的应用
- JavaScript 语法中:for..of,展开语法(spread syntax),yield,解构赋值。
- 创建对象时:new Set(iterable), new Map(iterable), new WeakMap(iterable), new WeakSet(iterable)等等。
- 一些方法的调用:Promise.all(itrable), Promise.race(iterable), Array.from(iterable)等等。
# 可迭代对象
const obj = {
val: ['aaa', 'bbb', 'ccc'],
[Symbol.iterator]() {
let index = 0;
return {
// 这里的next用到箭头函数,是为了能够让this指向obj这个对象。
next: () => {
return {
done: this.val.length <= index,
value: this.val[index++],
};
},
};
},
};
for (const i of obj) {
console.log(i); // 打印结果分别是 aaa, bbb, ccc
}
# 迭代器中断
- 迭代器在某些情况下会在没有完全迭代的情况下中断:
- 比如遍历过程中通过break,continue,return,throw中断的循环操作。
- 比如在解构的时候,没有解构所有的值。
- 如果想要监听中断的话,可以添加 return 方法。return 方法需要返回一个对象,如果没有返回一个对象会报错。
const obj = {
val: ['aaa', 'bbb', 'ccc'],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
return {
done: this.val.length <= index,
value: this.val[index++],
};
},
return: () => {
// 当迭代器中断时,会来到该函数
// 必须返回一个对象
console.log('迭代器提前终止了');
return { done: true };
},
};
},
};
# Generator
协程在 JavaScript 中的实现 ———— Generator 函数
# 什么是生成器?
生成器是 ES6 新增的一种函数控制,使用的方案。它可以让我们更加灵活的控制函数什么时候继续执行,暂停执行等。
生成器函数也是一个函数,但是和普通函数有一些区别:
- 生成器函数需要在 function 的后面加上一个符号 *
- 生成器函数可以通过yield关键字来控制函数的执行流程。
- 生成器函数的返回值是一个Generator(生成器)。生成器实际上是一种特殊的迭代器。
# 生成器函数代码
function* foo() { // 这里函数定义是有 * 的
console.log(1)
yield 1 // 这里yield后面的值会被作为返回的迭代器对象的value值
console.log(2)
yield 2
console.log(3)
}
const generator = foo() // generator就是一个生成器
// 执行到第一个yield并且暂停
console.log(generator.next()) // 1 { value: 1, done: false }
// 执行到第二个yield并且暂停
console.log(generator.next()) // 2 { value: 2, done: false }
// 执行剩余代码
console.log(generator.next()) // 3 { value: undefined, done: true }
- 在执行 foo 函数时,会发现函数体根本没有执行,它只是返回了一个生成器对象。
- 如果想要执行函数体里面的代码,调用 next 即可。
- 如果不希望 next 返回的迭代器对象的 value 的值为 undefined,可以通过yield 来返回结果。
# 生成器函数传递参数-next()
- 既然生成器可以暂停来分段执行,我们可以给每个分段来传递参数。
- 我们在调用 next 函数时,可以给 next 函数传递参数,那么这个参数会作为上一个 yield 语句的返回值。
- 一般情况下,不会给第一个 next 传递参数,因为第一个 next 前面没有 yield
function* bar() {
const num1 = yield 'a';
const num2 = yield num1;
const num3 = yield num2;
}
const generator = bar();
console.log(generator.next()); // { value: 'a', done: false }
console.log(generator.next('b')); // { value: 'b', done: false }
console.log(generator.next('c')); // { value: 'c', done: false }
console.log(generator.next()); // { value: undefined, done: true }
# 提前结束生成器函数-return 函数
- return 函数也可以给生成器函数传递参数。不过执行 return 函数后,生成器函数就会结束,之后调用 next 也不会继续生成值了。当调用 return 时,value 为return 传入的参数。
function* bar() {
const val1 = yield 'a';
console.log('val1', val1); // 这里是不会被执行的,因为调用了return函数
}
const generator = bar();
console.log(generator.next()); // { value: 'a', done: false }
console.log(generator.return(1)); // { value: 1, done: true }
console.log(generator.next()); // { value: undefined, done: true }
# 生成器抛出异常-throw 函数
- 除了可以给生成器函数内部传递参数之外,也可以该生成器行内部抛出异常。
- 抛出异常后,可以在生成器函数中捕获异常。
function* bar() {
console.log('start');
try {
yield 1;
} catch (err) {
console.log('生成器函数内部捕获异常', err); // 这里会捕获到 'err message'
yield 'catch内部使用yield';
}
yield 'catch外部使用yield';
}
const generator = bar();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.throw('err message')); // { value: 'catch内部使用yield', done: false }
console.log(generator.next()); // { value: 'catch外部使用yield', done: false }
# 生成器替代迭代器
- 生成器是一种特殊的迭代器,在某些情况下可以使用生成器替代迭代器。
// 实现让对象能够使用for...of遍历
function* generator() {
const keys = Object.keys(this);
for (let i = 0; i < keys.length; i++) {
yield this[keys[i]];
}
}
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
[Symbol.iterator]: generator,
};
for (const item of obj) {
console.log(item); // 1, 2, 3, 4
}
# await async
# 异步编程终极方案,用同步的方式,执行异步操作。
在 async 函数中,await 规定了异步操作只能一个一个排队执行,从而达到用同步的方式,执行异步操作的效果。
await 只能在 async 中使用,否则会报错!
await 后如果跟随的不是 Promise,可能会造成同步触发的效果。
function request(num) {
// 去掉Promise
setTimeout(() => {
console.log(num * 2);
}, 1000);
}
async function fn() {
await request(1); // 2
await request(2); // 4
// 1秒后执行完 同时输出
}
fn();
总结:
- await 只能在 async 函数中使用,不然会报错
- async 后面会跟上一个表达式,这个表达式会返回一个 Promise
- await 会等到 Promise 状态变成 fulfilled,才继续执行异步函数
- 如果 await 后面是一个普通的值,会直接返回
- 如果 async 抛出了异常,程序不会报错,而是作为 Promise 的 reject 来传递
# generator 实现 async
function generatorToAsync(generatorFn) {
return function () {
const gen = generatorFn.apply(this, arguments); // gen有可能传参
// 返回一个Promise
return new Promise((resolve, reject) => {
function go(key, arg) {
let res;
try {
res = gen[key](arg); // 这里有可能会执行返回reject状态的Promise
} catch (error) {
return reject(error); // 报错的话会走catch,直接reject
}
// 解构获得value和done
const { value, done } = res;
if (done) {
// 如果done为true,说明走完了,进行resolve(value)
return resolve(value);
} else {
// 如果done为false,说明没走完,还得继续走
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(
(val) => go('next', val),
(err) => go('throw', err)
);
}
}
go('next'); // 第一次执行
});
};
}
const asyncFn = generatorToAsync(gen);
asyncFn().then((res) => console.log(res));
# 测试对比一下:
# async/await 版本
async function asyncFn() {
const num1 = await fn(1);
console.log(num1); // 2
const num2 = await fn(num1);
console.log(num2); // 4
const num3 = await fn(num2);
console.log(num3); // 8
return num3;
}
const asyncRes = asyncFn();
console.log(asyncRes); // Promise
asyncRes.then((res) => console.log(res)); // 8
# 自执行 generator
function* gen() {
const num1 = yield fn(1)
console.log(num1) // 2
const num2 = yield fn(num1)
console.log(num2) // 4
const num3 = yield fn(num2)
console.log(num3) // 8
return num3
}
const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8