ES——Iterator,Generator,async
一、Iterator 遍历器
遍历器是一种机制,是一种接口,为各种不同的数据结构提供统一的访问机制
任何数据结构只要部署 Iterator 接口就能完成遍历(依次处理该数据结构的所有成员)
1、主要作用
- 为各种数据结构提供统一的、简便的访问接口
- 使数据结构的成员能按照某种次序排列
- 主要供 for…of 消费
2、Iterator遍历过程
遍历器本质上是一个指针对象
- 创建一个指定指向数据结构起始位置
- 每调用一次next方法,指针就指向下一个成员:第一次第一个成员,第二次第二个成员…
- 不断调用 next 直到结束为止
每次调用 next 都会返回当前成员信息:包含 value(当前成员的值) 和 done(boolean,遍历是否结束) 两个属性的对象
Demo1. 模拟 next 方法返回值的例子
var makeIterator = (array) => {
// 记录指针位置
var nextIndex = 0;
return {
next: ()=> {
nextIndex<array.length ?
console.log({value: array[nextIndex++], done:false}):
console.log({value: undefined, done: true})
}
}
}
var it = makeIterator(['a', 'b'])
it.next() // { value: 'a', done: false }
it.next() // { value: 'b', done: false }
it.next() // { value: undefined, done: true }
3、默认Iterator接口
目的:为所有的数据结构提供一种统一的访问机制,即 for…of 循环,该循环会自动的去寻找 Iterator 接口
默认的 Iterator 接口部署在 Symbol.iterator 属性,可以说,只要拥有该属性该数据结构就是“可遍历”
原生具有 Iterator 接口的数据结构
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
对象之所以没有,是因为其遍历前后顺序是不确定的
本质上遍历器是一种线性处理,对所有非线性的数据结构,部署遍历器就是不是一种线性转换
Demo2. 给对象部署 Iterator 接口
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() {return this;}
next() {
let val = this.value
if(val < this.stop) {
this.value++
return {done: false, value: val}
}
return {done: true, value: undefined}
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for(let v of range(0,10)) {
console.log(v);
}
4、默认调用Iterator接口的场合
- 解构赋值
- 扩展运算符
- yield*
- else
5、字符串的Iterator接口
二、Generator 生成器
1、基本概念
Generator 函数返回一个遍历器对象,代表Generator函数的内部指针(此对象本身具有Symbol.Iterator属性)
每调用遍历器对象的next方法都会返回一个有着 value 和 done 两个属性的对象,value 表示内部状态值,done 表示遍历是否结束
超出之后,会返回 undefined
function *helloGenerator() {
yield 'hello';
yield 'Paniliy';
return 'Bayern';
}
let g = helloGenerator()
g.next() // { value: 'hello', done: false }
g.next() // { value: 'Paniliy', done: false }
g.next() // { value: 'Bayern', done: true }
g.next() // { value: undefined, done: true }
yield 表达式
yield 语句与 return 的相似与区别
与Iterator接口的关系
上一章:任意一个对象的 Symbol.iterator 方法等于该对象的遍历器对象生成函数,调用该函数返回该对象的一个遍历器对象
赋给对象 Iterator 接口的方法:因为 Generator 函数就是遍历器生成函数,所以可以把 Generator 赋值给对象的 Symbol.iterator 属性
Question1. Iterator 接口只会访问 yield,而不会访问 return 的 value
因为一旦 next 方法的返回对象 done 属性为 true,for…of 循环就会终止,且不包含返回对象
Question2. 为什么 node 规定回调函数的第一个参数,必须是错误对象err
(如果没有错误,该参数就是null
)?
原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段
callback hell
协程(coroutine
异步任务:分为多段执行的任务
Thunk
传值调用
传名调用(在没有用上此参数时,船名调用更高效
对传名调用的实现,即将参数放到一个临时函数中,再将临时函数传入函数体,该临时函数就是Thunk函数
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
任何函数,只要参数有回调函数,就能写成Thunk函数的形式
Demo readFile Thunk函数转换
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
Demo Thunk函数转换器
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
// ES6版本
const Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
Thunkify
三、async
1、含义
就是 Generator 的语法糖
进一步说,async函数可以看做多个异步操作包装成一个Promise对象,而await就是then的语法糖
Note. async 对 Generator 的改造体现在以下四点
- 内置执行器:不需要next方法或co模块
- 更好的语义:async异步,await等待前面的结果
- 更广泛的适用性:co模块规定yiled后面只能是Chunk函数或者Promise对象,而async后面可以是Promise对象或者原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
- 返回值是Promise,比Generator返回值是Iterator方便
2、基本用法
async 函数返回的是Promise对象
Demo1. 延时输出
// function timeout(ms) {
// return new Promise((resolve) => {
// setTimeout(resolve, ms);
// })
// }
// 使用async编写timeout
async function timeout(ms) {
await new Promise((resolve => {
setTimeout(resolve, ms)
}))
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello async', 2000);
async 使用形式
async function foo(){};
const foo = async function(){};
const foo = async ()=>{}
// 对象式
let obj = {async foo(){}};
obj.foo().then(...)
// 类
class Storage {
constructor(){
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatar/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jack').then(...)
3、语法
Demo2. then的箭头函数连续写法
async function f(data) {
if(data>0) return 'hello';
else throw Error('Wrong');
}
f(-10).then(
v => console.log(v),
e => console.log(e)
)
Demo3. 休眠语法(JS本是没有的)
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const sleepTime = await new Sleep(1000);
console.log(sleepTime);
})();
// 1000
Question. How to 任何一个 await 语句后面的 Promise 对象变为reject状态时,整个 async 函数都会中断执行?
可以通过 try catch 实现即使前面的 reject 了后面依然执行
async function f() {
try {
await Promise.reject('Wrong');
} catch(e) {}
return await Promise.resolve('hello');
}
f()
.then(v => console.log(v))
Note. 需要注意的点
-
最好把 await 命令放在 try…catch中
-
多个wait命令后面的异步操作操作若不存在继发关系,最好同时触发
// 以下两种写法都能实现同步执行 // 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
-
await只能用在async函数中
-
async能够保留运行堆栈
a函数执行后启动异步任务b,b执行时a不会停止,b执行完a可以能已经结束了,此时b的上下文环境消失了
const a = () => { b().then(() => c()); }; const a = async () => { await b(); c(); };
将a函数改为async形式,b运行时a会中断等待
4、async实现原理
async function fn(args){...}
// 上下等同,所有的async函数都能改写为以下形式
function fn(args) {
return spawn(function *(){...})
}
spwan就是自动执行器,以下是实现
function spawn(genF) {
return new Promise((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(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
)
}
// 为什么是 undefined
step(() => gen.next(undefined));
})
}
5、异步比较
/**
* Promise 的写法
* 语法多为Promise的API,逻辑、语义难以看出
* @returns p Promise
*/
// ???????????????????? return p.then().then()...catch().then() ?????????????????
function chainAnimationsPromise(elem, animations) {
// ret:中转值,保存上一个动画的返回值;p:空的Promise
let ret = null;
let p = Promise.resolve();
// 循环加then方法到p上,以达到添加所有动画在p上的效果
for(let anim of animations) {
p = p.then(val => {
ret = val;
return anim(elem);
})
};
// 返回部署了错误捕捉机制的 Promise
return p.catch(e => {/*忽略错误继续执行*/}).then(() => ret);
}
/**
* Generator 的写法
* 此spawn函数是一个自动执行器,返回Promise对象,需要有yield来执行
* @returns spawn Function
*/
function chainAnimationsPromise(elem, animations) {
return spawn(function *() {
let ret = null;
try {
for(let anim of animations) {
ret = yield anim(elem)
}
} catch(e) {
/* 忽略错误继续执行 */
}
return ret;
})
}
/**
* async 的写法
* 简洁明了
* @returns
*/
async function chainAnimationsPromise(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem)
}
} catch(e) {
/* 忽略错误继续执行 */
}
return ret;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!