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 的改造体现在以下四点

  1. 内置执行器:不需要next方法或co模块
  2. 更好的语义:async异步,await等待前面的结果
  3. 更广泛的适用性:co模块规定yiled后面只能是Chunk函数或者Promise对象,而async后面可以是Promise对象或者原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
  4. 返回值是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. 需要注意的点

  1. 最好把 await 命令放在 try…catch中

  2. 多个wait命令后面的异步操作操作若不存在继发关系,最好同时触发

    // 以下两种写法都能实现同步执行
    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二
    let fooPromise = getFoo(); let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    
  3. await只能用在async函数中

  4. 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;
}

6、实例:按顺序完成异步操作

7、顶部 await(ES2022)

posted @   巴伐利亚药水哥  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示