可迭代对象及其相关的迭代器是ES6的一个特性。ES6新增的期约让编写异步代码更容易。关键字async和await是ES2017中引入的,为了简化异步编程提供的新语法,最后,异步迭代器和for/aswait循环是ES2018中引入的,允许在看起来同步的简单循环中操作异步事件流。
1 迭代器与生成器
迭代器3个不同类型:
1)可迭代对象,指的是任何具有专用迭代器方法,且该方法返回迭代器对象的对象。
2)迭代器对象,指的是任何具有next()方法,且该方法返回迭代结果对象的对象。
3)迭代结果对象,具有属性value和done的对象。
可迭代对象的迭代器方法没有使用惯用名称,而是使用了符号Symbol.iterator作为名字。
迭代器本身也是可迭代的。(它们也有Symbol.iterator方法,且返回它们自己。)
1.1 实现可迭代对象
function names() {
let nameArr = ["Tom","Jerry","Lucy"];
let current = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
if (current < nameArr.length) {
return {value: nameArr[current++],done: false};
}
return { done: true };
},
return() {
console.log("迭代器被终止了");
return {done: true};
}
}
}
let iterator = names();
for (let it of iterator) {
if (it === "Jerry") break;
console.log(it);
}
// Tom
// 迭代器被终止了
迭代器的return()方法,如果迭代在next()方法done属性为true的结果前停止(常见原因是通过break语句提前退出for/of循环),那么解释器会检查迭代器对象是否有return()方法,如果有,则会调用它。这个return()方法必须返回一个迭代器结果对象,这个对象的属性会被忽略,但返回非对象值会导致报错。
1.2 生成器
语法上类似常规的JS函数,但使用关键字是function*。调用生成器并不会实际执行函数体,而是返回一个生成器对象(是一个迭代器)。调用它的next()方法会导致生成器函数的函数体从头(或从当前位置)开始执行,直至遇见一个yield语句。yield语句的值会成为调用迭代器的next()方法的返回值。
function* numIterator() {
console.log("numIterator begin");
yield 1;
console.log("yield 1 finished");
yield 2;
console.log("yield 2 finished");
yield 3;
console.log("yield 3 finished");
console.log("numIterator begin")
}
let iterator = numIterator();
iterator.next(); // numIterator begin
iterator.next(); // yield 1 finished
iterator.next(); // yield 2 finished
iterator.next(); // yield 3 finished numIterator begin
iterator.next(); // 空输出
1.3 高级生成器特性
1)生成器函数的返回值。生成器函数可以有return语句,只是其返回值在迭代的时候会被忽略。但是手工迭代时可通过显示调用next()得到:
function* numIterator() {
yield 1;
yield 2;
return "hello";
}
console.log(...numIterator()); // 1 2
let iterator = numIterator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 'hello', done: true }
console.log(iterator.next()); // { value: undefined, done: true }
2)yield表达式的值。yield是一个表达式,可以有值的。调用生成器next()方法时,生成器函数会一直运行直到到达一个yield表达式,yield关键字后面的表达式会被求值,该值成为next()调用的返回值。此时生成器函数在求值yield表达式中途停了下来。下次调用生成器的next()方法时,传给next()的参数会变成暂停的yield表达式的值。
function* numIterator() {
console.log("begin");
let y1 = yield 1;
console.log("y1:",y1);
let y2 = yield 2;
console.log("y2:",y2);
}
let iterator = numIterator();
iterator.next("a"); // begin
iterator.next("b"); // y1:b
iterator.next("c"); // y2:c
3)生成器的try/finally。在实现迭代器对象时可定义return()方法,但是在生成器不行,但是生成器可以通过try/ finally来实现同等效果。
function* numIterator() {
try{
yield 1;
yield 2;
yield 3;
} finally {
console.log("迭代")
}
}
console.log(...numIterator());// 迭代 1 2 3
for (let it of numIterator()) {
console.log(it);
}
// 迭代
// 1 2 3
// 1
// 2
// 3
// 迭代
2 异步JS
期约是一种为简化异步编程而设计的核心语言特性。
2.1 期约 Promise
期约是一个对象,表示异步操作的结果。这个结果可能就绪也可能未就绪。
以动词开头来命名返回期约的函数是一种惯例。
function customPro() {
console.log("异步函数");
return new Promise((resolve,reject) => {
resolve("hello 回调函数");
});
}
function showCallBackInfo(res) {
console.log("回调1",res);
}
customPro().then(showCallBackInfo);
// 异步函数
// 回调1 hello 回调函数
2.1.1 处理错误
期约有两种处理错误的方式:
1)then()第二个参数传递一个处理错误的函数。
2)catch()方法。
function customPromise(type) {
console.log("异步函数");
return new Promise((resolve,reject) => {
if (type % 2 === 0) {
resolve(type % 2);
} else {
reject(new Error("不能为基数"));
}
});
}
function showCallBackInfo(type) {
console.log("回调函数:" + type)
}
function catchError(e) {
console.log(e);
}
customPromise(1).then(showCallBackInfo,catchError); // 异步函数 Error: 不能为基数 (报错)
customPromise(0).then(showCallBackInfo,catchError); // 异步函数 回调函数:0
customPromise(1).then(showCallBackInfo).catch(catchError); // 异步函数 Error: 不能为基数 (报错)
catch(),会捕获期约链上在它位置前面所有期约的错误(如果该期约的错误没被处理的话)。
fun().then(call1).then(call2).catch(errCatch); //catch会捕获fun、call1的错误。
fun().then(call1).catch(errCatch1).then(call2).catch(errCatch2); //errCatch2只会捕获call1的错误,因为fun的错误已被errCatch1捕获并处理(未抛出)。
2.1.2 并行期约
1) Promise.all()接收一个期约对象数组作为输入,返回一个期约。如果输入期约中任意一个拒绝,返回的期约也将拒绝;否则,返回期约会以每个输入期约兑现值的数组兑现。
2)Promise.allSettled()接收一个期约对象数组作为输入,永远不拒绝返回的期约,会等所有输入期约全部落定后兑现。其返回的期约解决为一个对象数组,其中每个对象都应对应一个输入期约,且都有一个status属性,值为fulfilled或rejected,如果status属性值为fulfilled,值该对象还有个value属性,包含兑现的值。如果status为rejected,那该对象还有个reason属性。
3)Promise.race(),接收一个期约对象数组作为输入,返回一个期约,这个期约会在输入数组中的期约有一个兑现或拒绝时马上兑现或拒绝。
2.1.3 创建期约
1)从头开始创建期约。
function customPromise(type) {
console.log("自定义异步函数");
// 创建新期约
return new Promise((resolve,reject) => {
if (type < 0) reject(new Error("不能小于0"));
else resolve("你好啊");
});
}
function showCallBackInfo(info) {
console.log(info);
}
function dealErr(error) {
console.log(error.message);
}
customPromise(2).then(showCallBackInfo).catch(dealErr);
customPromise(-1).then(showCallBackInfo).catch(dealErr);
// 自定义异步函数
// 自定义异步函数
// 你好啊
// 不能小于0
2)基于其他期约的期约。
function customPromise() {
return new Promise((resolve) => {
resolve();
})
}
function customPromise2() {
return customPromise().then(_ => "hello");
}
customPromise2().then(res => {
console.log("res:",res); //res:hello
})
3)基于同步值的期约。
Promise.resolve() 接收一个值作为参数,并会立即(异步)以该值兑现一个期约。Promise.reject()则返回以该参数作为理由而拒绝的期约。
Promise.resolve("hello").then(res => {
console.log("res:",res); // res: hello
})
2.2 async 和 await
ES2017 新增了async和await两个关键字。
function sleep(time) {
return new Promise(resolve => setTimeout(resolve,time * 1000))
}
async function countDown() {
let begin = new Date();
console.log("开始");
await sleep(1);
await sleep(2);
await sleep(2);
let end = new Date();
console.log("结束");
return end.getTime() - begin.getTime();
}
countDown().then(time => {
console.log("耗时:" + time / 1000 + "s");
});
// 开始
// 结束
// 耗时:5.009s
实现细节,编译器将async函数包装成一个返回期约的函数。