高级JS-迭代器-生成器-异步函数async await

165 阅读7分钟

迭代器

在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议:

  1. 迭代器协议定义了产生一系列值(无论是有限还是无限)的标准方式
  2. 在JavaScript中这个标准就是一个特定的next方法

next方法:

  • 无参数或一个参数的函数
  • 返回一个拥有 done:Boolean、value:具体值/undefined 两个属性
  • done:true 序列已经迭代完,value 可以省略;false 序列中可以产生下一个值
const names = ['aaa', 'bbb']
const nums = [12,23]

// 给names创建一个迭代器
function createArrayIterator(arr) {
    let index = 0
    return {
        next() {
            if (index < arr.length) {
                return {done: false, value: arr[index++]}
            }else {
                return {done: true}
            }
        }
    }
}

const namesIterator = createArrayIterator(names);
console.log(namesIterator.next()); // {done: false, value: 'aaa'}
console.log(namesIterator.next()); // {done: false, value: 'bbb'}
console.log(namesIterator.next()); // {done: true}

const numsIterator = createArrayIterator(nums)
console.log(numsIterator.next()); // {done: false, value: '12'}
console.log(numsIterator.next()); // {done: false, value: '23'}

可迭代对象

当一个对象变成一个可迭代对象的时候,就可以进行for...of 操作,它的本质是调用@@iterator

  1. 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
  2. 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性

将info变成一个可迭代对象

必须实现一个特定的函数:[Symbol.iterator]

这个函数需返回一个迭代器,这个迭代器用于迭代当前这个对象

const infos = {
    friends: ['www', 'qqq', 'wqq'],
    [Symbol.iterator]() {
        let index = 0
        const infosIterator = {
        // next如果是普通函数,this就会是迭代器
            next: () => {
                if (index < this.friends.length) {
                    return {done: false, value: this.friends[index++]}
                } else {
                    return {done: true}
                }
            }
        }
        return infosIterator
    }
}

const iterator = infos[Symbol.iterator]()
console.log(iterator.next()); // {done: false, value: 'www'}
console.log(iterator.next()); // {done: false, value: 'qqq'}

// 可迭代对象可以进行for...of操作
for (const item of infos) {
    console.log(item); // www qqq wqq
}

// 可迭代对象必然有一个[Symbol.iterator]函数
const stu = ['aa', 'bb']
const stuIterator = stu[Symbol.iterator]()
console.log(stuIterator.next()); // {done: false, value: 'aa'}
console.log(stuIterator.next()); // {done: false, value: 'bb'}
console.log(stuIterator.next()); // {done: true, value: undefined}

迭代key/value

// 迭代infos的key/value
const infos = {
    name:'www',
    age:'19',
    height:1.66,
    [Symbol.iterator](){
      console.log(this,'this');
      // const keys = Object.keys(this) // 拿到key
      // const value = Object.values(this) // 拿到value
      const entries = Object.entries(this)  // 拿到key,value
      let index = 0
      return {
        next() {
          if (index < entries.length) {
            return {done: false, value: entries[index++]}
          } else {
            return {done: true}
          }
        }
      }
    }
}

for (const item of infos) {
    const [key,value] = item
    console.log(key, value); // name www...
}

原生可迭代器对象

平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

String、Array、Map、Set、arguments对象、NodeList集合

可迭代对象应用

JavaScript中语法:for ...of、展开语法、yield*、解构赋值

创建一些对象时:new Map()new WeakMap()new Set()new WeakSet()

一些方法的调用:Promise.all()Promise.race()Array.from()

自定义类的迭代

class Person {
    constructor(name,age,friends) {
        this.name = name
        this.age = age
        this.friends = friends
    }

    // 添加实例方法
    [Symbol.iterator]() {
        let index = 0
        const iterator = {
            next:() => {
                if(index < this.friends.length) {
                    return {done:false,value:this.friends[index++]}
                }else {
                    return {done: true}
                }
            },
            return(){
            console.log('迭代器中断了'); // 便利过程中通过break、return、throw,解构的时候没有解构所有的值
            return {done: true}
            }
        }
        return iterator
    }
}

// 语法糖
class Person {
    constructor(name,age,friends) {
        this.name = name
        this.age = age
        this.friends = friends
    }

    *[Symbol.iterator]() {
        yield* this.friends
    }
}

// 都变成可迭代对象
const p1 = new Person('www',12,['aa','bb','cc'])

for (const item of p1) {
    console.log(item);
}

生成器

生成器是ES6中新增的一种函数控制使用的方案,可以更加灵活的控制函数什么时候继续执行、暂停执行等

  1. 生成器函数需要在function的后面加一个符号:*
  2. 代码的执行可以通过yield关键字来控制
  3. 生成器函数的返回值是一个生成器(生成器:是一种特殊的迭代器)
// 定义生成器函数
function* foo() {
    console.log('1111');
    yield
    console.log('2222');
    yield
    console.log('3333');
}

const generator = foo() // 返回一个生成器对象
// 调用next方法执行函数,遇到yield就中断,继续执行再第=调用next
generator.next()
generator.next()

生成器传递参数

function* foo(meg1) {
    console.log('1111',meg1);
    const meg2 = yield 'aaa'
    console.log('2222');
    // return 'rrr' 被return后,调用next不会继续生成值了
    console.log('3333',meg2);
    const meg3 = yield 'bbb'
    console.log('4444',meg3);
}

const generator = foo('meaagee1111') // 第一次传参
console.log(generator.next()); // {value: 'aaa', done: false}
console.log(generator.next('meaagee3333')); // {value: 'bbb', done: false}
// 提前结束函数
console.log(generator.return('结束')); 
// 抛出异常
console.log(generator.throw(new Error('异常'))); 
console.log(generator.next('meaagee4444')); // {value: undefined, done: true}

生成器替代迭代器

const names = ['aaa', 'bbb']

function createArrayIterator1(arr) {
    let index = 0
    return {
        next() {
            if (index < arr.length) {
                return {done: false, value: arr[index++]}
            }else {
                return {done: true}
            }
        }
    }
}

// 对createArrayIterator1进行重构
function* createArrayIterator2(arr) {
    for (let i = 0;i< arr.length;i++){
        yield arr[i]
    }
}

// 可以使用yield*来生产一个可迭代对象
// 相当于yield语法糖,会依次迭代这个可迭代对象,每次迭代其中一个值
function* createArrayIterator(arr) {
    yield* arr
}

const namesIterator = createArrayIterato2(names);
console.log(namesIterator.next()); // {done: false, value: 'aaa'}
console.log(namesIterator.next()); // {done: false, value: 'bbb'}
console.log(namesIterator.next()); // {done: true}

生成某个范围之间的值

function* createRangeGenerator(start, end) {
    for (let i = start; i < end; i++) {
        yield i
    }
}

const range = createRangeGenerator(3, 5)
console.log(range.next()); // {value: 3, done: false}
console.log(range.next()); // {value: 4, done: false}
console.log(range.next()); // {value: undefined, done: true}

异步请求

// 封装一个请求的方法:url -> promise(result)
function requestData(count) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(count)
        }, 2000)
    })
}
// 发送一次网络请求
requestData('https://wcc.com').then(res => console.log(res))

我们需要向服务器发送网络请求获取数据,一共需要发送三次请求

第二次的请求url依赖于第一次的结果

第三次的请求url依赖于第二次的结果

方式一:层层嵌套(回调地狱)

function getDate1() {
    // 第一次请求
    requestData('www').then(res1 => {
        console.log('第一次的结果', res1)
        // 第二次请求
        requestData(res1 + 'qqq').then(res2 => {
            console.log('第二次的结果', res2)
            // 第三次请求
            requestData(res2 + 'rrr').then(res3 => {
                console.log('第二次的结果', res3)
            })
        })
    })
}
getDate1()

方式二:使用 promise 链式调用(解决回调地狱)

function getDate2() {
    // 第一次请求
    requestData('www').then(res1 => {
        console.log('第一次的结果', res1)
        return requestData(res1 + 'qqq')
    }).then(res2 => {
        console.log('第二次的结果', res2);
        return requestData(res2 + 'rrr')
    }).then(res3 => {
        console.log('第三次的结果', res3);
    })
}
getDate2()

方式三:生成器

function* getDate3() {
    const res1 = yield requestData('www') // yield会等到requestData('www')有返回值,才会继续往下执行
    console.log('第一次的结果', res1)

    const res2 = yield requestData(res1 + 'qqq')
    console.log('第二次的结果', res2);

    const res3 = yield requestData(res2 + 'rrr')
    console.log('第三次的结果', res3);

}
const generator = getDate3()
generator.next().value.then(res1 => {
    generator.next(res1).value.then(res2 => {
        generator.next(res2).value.then(res3 => {
            generator.next(res3)
        })
    })
})

// 自动化执行生成器函数(了解)
function execGenFn(genFn) {
    // 1. 获取对应函数的generator
    const generator = genFn()
    // 2. 定义一个递归函数
    function exec(res) {
        const result = generator.next(res) // 对象类型:{done:'true',value:'值'}
        if(result.done) return 
        result.value.then(res => {
            exec(res)
        })
    }
    exec() // 执行递归函数
}
execGenFn(getDate3)

方式四:async/await(最终方案)

async function getDate4() {
    const res1 = await requestData('www')
    console.log('第一次的结果', res1)

    const res2 = await requestData(res1 + 'qqq')
    console.log('第二次的结果', res2);

    const res3 = await requestData(res2 + 'rrr')
    console.log('第三次的结果', res3);
}

getDate4().catch(err => {
    console.log('err:', err);
})

异步函数

// 异步函数写法
async function foo1() {}
const foo2 = async () => {}
const foo3 = async function() {}
class Person {
  async foo4() {}
}

异步函数返回值

情况一:异步函数可以有返回值,返回值相当于被包裹到Promise.resolve中

情况二:如果我们的异步函数的返回值是Promise,状态由会由Promise决定

情况三:如果异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定

如果在async中抛出了异常,程序不会像普通函数一样报错,而是会作为promise的reject来传递

async function foo() {
    // 1.返回普通的值
    return [12,31]

    '123'.filter()  // 报错会作为promise的reject来传递
    
    // 2. 返回一个promise
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('aaa')
        },2000)
    })

    // 3. 返回thenable对象
    return {
        then(resolve, reject) {
            setTimeout(() => {
                resolve('bbb')
            }, 2000)
        }
    }
}

foo().then(res => console.log(res))
    .catch(err => {
        console.log(err)
      }
)

异步函数 async function

普通函数不能使用await

  1. 使用await是后面会跟上一个表达式,这个表达式会返回一个Promise
  2. await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数
  3. 如果await后面是一个普通的值,那么会直接返回这个值
  4. 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值
  5. await 后面返回的Promise是 reject 状态,会将这个 reject 结果直接作为函数的Promise的reject值
// 1. 定义一些其他函数
async function test() {
    console.log('test function');
    const res = 'test'
}

async function bar() {
    console.log('bar function');

    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('bar')
        },2000)
    })
}

function demo() {
    console.log('demo function');
    return{
        then(resolve) {
            resolve('demo')
        }
    }
}

// 2. 调用的入口函数
async function foo() {
    const res1 = await test()
    const res2 = await bar()
    const res3 = await demo()
}
foo()