迭代器-可迭代对象-生成器-async/await

853 阅读11分钟

一、迭代器

迭代: 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代

迭代器: 迭代器是对某一个数据结构进行遍历的对象,符合迭代器协议(可以产生一系列值).迭代器协议有一个next方法,是一个无参或一个参数的函数,每一次迭代都会返回{done: false, value: 'xxx'}, 当所有数据迭代结束最后默认会返回{done: true, value: undefined}

    let iterator = {
        next: function() {
            return { done: false, value: 123 }
        }
    }
    console.log(iterator.next()) // { done: false, value: 123 }
    const arr = [1, 3, 5, 6, 21]
    let index = 0
    let iterator1 = {
        next: function() {
            if(index < arr[index]) {
                return { done: false, value: arr[index++] }
            } else {
                return { done: true, value: undefined }
            }
        }
    }
    console.log(iterator1.next()) // { done: false, value: 1 }
    console.log(iterator1.next()) // { done: false, value: 3 }
    console.log(iterator1.next()) // { done: false, value: 5 }
    console.log(iterator1.next()) // { done: false, value: 6 }
    console.log(iterator1.next()) // { done: false, value: 21 }
    console.log(iterator1.next()) // { done: true, value: undefined }
    console.log(iterator1.next()) // { done: true, value: undefined }

每次调用next方法,都会返回一个对象,这个对象有两个属性(done: 是否迭代结束, value: 迭代返回的结果);一旦迭代结束,再次调用next,结果默认都为{ done: true, value: undefined }

二、可迭代对象

可迭代对象: 可迭代对象也是一个对象,符合可迭代协议.可迭代协议有一个[Symbol.iterator]方法,这个方法会返回一个迭代器对象

    let obj = {
        name: ['小红', '小刚', '小强', '小王'],
        [Symbol.iterator]: function() {
            let index = 0
            return {
                next: () => {
                    if(index < this.name.length) {
                        return { done: false, value: this.name[index++] }
                    } else {
                        return { done: true, value: undefined }
                    }
                }
            }
        }
    }
    const iterator = obj[Symbol.iterator]()
    console.log(iterator.next()) // { done: false, value: '小红' }
    console.log(iterator.next()) // { done: false, value: '小刚' }
    console.log(iterator.next()) // { done: false, value: '小强' }
    console.log(iterator.next()) // { done: false, value: '小王' }
    console.log(iterator.next()) // { done: true, value: undefined }
    console.log(iterator.next()) // { done: true, value: undefined }

    const iterator1 = obj[Symbol.iterator]()
    console.log(iterator1.next()) // { done: false, value: '小红' }
    console.log(iterator1.next()) // { done: false, value: '小刚' }

1.如何判断某个数据是否为可迭代对象

    function isIterable(obj) {
        return typeof obj[Symbol.iterator] === 'function'
    }
    console.log(isIterable([1, 3, 5])) // true
    console.log(isIterable('hello')) // true
    console.log(isIterable(new Map())) // true
    console.log(isIterable(new Set())) // true
    console.log(isIterable(123)) // false
    console.log(isIterable({name: 'ywq'})) // false

可迭代对象符合可迭代协议(有Symbol.iterator方法),因此我们可以通过判断对象是否有Symbol.iterator方法来确定他是否是一个可迭代对象...

我们将上面的数组、字符串更换成其他不同数据的数组、字符串,发现它仍然是一个可迭代对象. 这是为什么呢? 嗯,原来是它内部已经帮我们实现了,那我们来看一下都有哪些是官方已经帮我们实现了吧

2.常见的内置可迭代对象

    // 数组
    const nums = [21, 18, 7, 3]
    console.log(nums[Symbol.iterator]) // [Function: values]

    // 字符串
    const str = '你好呀'
    console.log(str[Symbol.iterator]) // [Function: [Symbol.iterator]]

    // map
    const map = new Map()
    console.log(map[Symbol.iterator]) // [Function: entries]

    // set
    const set = new Set()
    console.log(set[Symbol.iterator]) // [Function: values]

    // arguments参数
    function foo() {
        console.log(arguments[Symbol.iterator]) // [Function: values]
    }
    foo(10, 20, 30)

我们发现 数组、字符串、map、set、arguments参数 都是内置的可迭代对象,但是这些可迭代对象有什么用呢,有什么应用场景?

3.可迭代对象的应用场景

  • 3.1 场景一: for...of
    const nums = [10, 3, 62, 97]
    for(const item of nums) {
        console.log(item) // 依次打印 10 3 62 97
    }
  • 3.2 场景二: 展开语法
    const arr = ['aaa', 'bbb', 'ccc']
    const myIterable = {
        data: 'ywq',
        [Symbol.iterator]: function() {
            let index = 0
            return {
                next: () => {
                    if(index < this.data.length) {
                        return { done: false, value: this.data[index++] }
                    } else {
                        return { done: true, value: undefined }
                    }
                }
            }
            
        }
    }
    const newArr = [...arr, ...myIterable]
    console.log(newArr) // [ 'aaa', 'bbb', 'ccc', 'y', 'w', 'q' ]
  • 3.3 场景三: 解构语法
    let arr = ['小张', '小杨', '小王']
    let [ name1, name2 ] = arr
    console.log(name1, name2) // 小张 小杨
  • 3.4 场景四: 创建其他的数据 Map/Set接收可迭代对象
    let arr = [1, 3, 9, 12]
    let obj = { name: 'ywq', age: 18 }
    const map = new Map(arr) // Map(3) { 'ywq' => 18, 'wqy' => 12, 'qwy' => 21 }
    const map = new Map(obj) // TypeError: object is not iterable

    const set = new Set(arr) // Set(4) { 1, 3, 9, 12 }
    const set = new Set(obj) // TypeError: object is not iterable
  • 3.5 场景五: Promise.all/Promsie.allSettle .....
    const myIterable = {
        data: 'ywq',
        [Symbol.iterator]: function() {
            let index = 0
            return {
                next: () => {
                    if(index < this.data.length) {
                        return { done: false, value: this.data[index++] }
                    } else {
                        return { done: true, value: undefined }
                    }
                }
            }
            
        }
    }
    Promise.all(myIterable).then(res => { console.log(res) }) // [ 'y', 'w', 'q' ]

上面是可迭代对象的应用场景,你可能会产生以下疑问...

  • 为什么for...of可以遍历可迭代对象

    可迭代对象实现了[Symbol.iterator]方法. 数据结构具有Symbol.iterator属性,就可以进行遍历. 对象的Symbol.iterator属性指向其默认遍历器方法

    使用for...of时,会自动调用[Symbol.iterator]方法,返回给我们一个遍历器对象,然后将遍历器对象的value返回给我们

  • 为什么普通的对象也可以有展开和解构语法

    eg: let obj = { name: 'ywq', age: 18 } const newObj = {...obj} const { name, age } = obj

    我们通过上面可以判断obj并不是一个可迭代对象.但是为什么obj也可以进行展开和解构. 这是因为对象的展开和解构使用的并不是可迭代操作,具体是怎么实现的,可以找度娘

三、生成器

生成器: 可以控制函数什么时候继续执行、什么时候暂停执行

  • 1.生成器是一个对象,是一个特殊的迭代器
  • 2.生成器函数的返回值是生成器(Generator)
  • 3.生成器函数的定义: function* 函数名称() {}; 通过yield控制函数的暂停和执行

下面我们来写一段简单的生成器函数吧

    function* test() {
        let num1 = 2
        console.log('num1:', num1) // // num1: 2
        yield

        let num2 = 3
        console.log('num2:', num2) // num2: 3
        yield

        console.log(num1 + num2) // 5
    }
    const generator = test()

    console.log(generator.next()) // { value: undefined, done: false }
    console.log(generator.next()) // { value: undefined, done: false }
    console.log(generator.next()) // { value: undefined, done: true }
    console.log(generator.next()) // { value: undefined, done: true }

生成器函数和普通函数的区别是:声明时多了一个*.

当调用test函数时,函数内部的代码并不会执行,而是会返回给我们一个生成器.

通过next调用这个生成器,第一个yield前面的代码会被执行;再次使用next,第一个yield和第二个yield之间的代码会被执行;第三次调用next,第二个yield和第三个yield之间的代码会被执行...依此类推

我们发现调用next方法打印的结果和前面的迭代器是一样的.因此可以得出生成器也是一个特殊的迭代器.

当第三次调用next方法时,迭代已经结束,done为true;但是前面两次的迭代结果都为undefined,我们怎么使其有值呢?

1.迭代器对象的结果

    function* test() {
        let num1 = 2
        console.log('num1:', num1)
        // return '哈哈哈' // ②
        yield // ③
        // yield '哈哈哈2' // ④

        let num2 = 3
        console.log('num2:', num2)
        yield 

        console.log(num1 + num2)
        return undefined
        // return '嘿嘿嘿' // ①
    }
    const generator = test()

    console.log(generator.next())
    console.log(generator.next())
    console.log(generator.next())

image.png

一个函数默认的返回值为undefined,我们猜想最后一次迭代结束的结果为undefined,我们将①放开,发现结果正如我们所想.

return的结果就是我们迭代对象返回的结果,那我们将②放开.发现done为true了,后面再调用next方法done仍然为true.

我们得出了一个结论:

1. yield 是特殊的 return

2. yield 会暂停生成器的执行; return 会终止生成器的执行

2.生成器的next传递参数 -> generator.next(arg)

    function* foo(num) {
        let v1 = 3 * num
        let aaa = yield
        
        let v2 = v1 * aaa
        let bbb = yield v2

        let v3 = v2 + bbb
        return v3
    }
    const generator = foo(6)
    console.log(generator.next()) // { value: undefined, done: false }
    console.log(generator.next(2)) // { value: 36, done: false }
    console.log(generator.next(5)) // { value: 41, done: true }

普通的函数调用可以传递参数,next当然也可以咯

当在next方法中传入参数时,参数会被显示在上一个yield的返回值中(第二次调用next, 参数是aaa;第三次调用next, 参数是bbb;依此类推);

第一次调用next方法,和我们普通函数的传参一样.通常第一次调用next是不传递参数的.

3.生成器的return终止执行 -> generator.return(msg)

    function* demo() {
        let str = '你好'
        const name = yield str

        let str1 = `我是${name}`
        const age = yield str1

        console.log(111)

        let str2 = `我今年${age}了`
        const address = yield str2
        
        let str3 = `我住在${address}`
        yield str3

        return '今天我很开心'
    }
    const generator = demo()
    const i1 = generator.next()
    console.log(i1) // { value: '你好', done: false }

    const i2 = generator.next('ywq')
    console.log(i2) // { value: '我是ywq', done: false }

    const f = generator.return('终止了')
    console.log(f) // { value: '终止了', done: true }

    console.log(generator.next()) // { value: undefined, done: true }
    console.log(generator.next()) // { value: undefined, done: true }
    console.log(generator.next()) // { value: undefined, done: true }

上面代码我们发现,当调用return方法时,生成器被终止了,done为true. 后面再次调用done仍为true.

代码中的console甚至不会执行,相当于在前后yield之间直接return了

4.生成器的throw抛出异常 -> generator.throw(errMsg)

    function* fn(num) {
        let value1 = 21 + num
        const p1 = yield value1

        let value2 = value1 + p1
        // const p2 = yield value2

        try {
            const p2 = yield value2
        } catch(err) {
            console.log('err:', err)          
        } 
        console.log('执行后续代码')             
    }
    const generator = fn(6)
    const v1 = generator.next()
    console.log(v1)

    const v2 = generator.next(10)
    console.log(v2)

    // const v3 = generator.next(3) // ①
    const v3 = generator.throw(3) // ②
    console.log(v3)

throw通常用于条件语句中,条件不符合就执行throw,符合的话,就按照正常逻辑执行

5.替代迭代器

    function IteratorFn(arr) {
        let index = 0
        return {
            next: function() {
                if(index < arr.length) return { done: false, value: arr[index++]}
                return { done: true, value: undefined }
            }
        }
    }
    let arr = [1, 21, 3, 72, 6]
    const iterator = IteratorFn(arr)

将上面代码替换为生成器

方法一: yield 值

    function* IteratorFn(arr) {
        yield 1
        yield 21
        yield 3
        yield 72
        yield 6
    }
    let arr = [1, 21, 3, 72, 6]
    const generator = IteratorFn(arr)

方法二: for...of

    function* IteratorFn(arr) {
        for(const item of arr) {
            yield item
        }
    }
    let arr = [1, 21, 3, 72, 6]
    const generator = IteratorFn(arr)

方法三: yield* 可迭代对象

    function* IteratorFn(arr) {
        yield* arr
    }
    let arr = [1, 21, 3, 72, 6]
    const generator = IteratorFn(arr)

6.异步代码解决方案

场景: 获取某省某市某区某校某年级某班某人的考试成绩 image.png

    // 公共代码
    let commonUrl =  'http://hnstykscg.com/getScore?'
    function getScore(url) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(url)
            }, 2000)           
        })
    }  
    getScore(commonUrl + 'provinceID=p41').then(res => {
        if(有该省份) {
            getScore(commonUrl + 'provinceID=p41&&cityID=ct03').then(res => {
                if(有该城市) {
                    getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06').then(res => {
                        if(有该区) {
                            getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06&&schoolID=s001').then(res => {
                                // ...
                            })
                        }                       
                    })
                }               
            })
        }       
    })

上面代码是假设的一个场景,我们多次调用了同一个函数,造成了'回调地狱'...当然上面场景还是有限的.总之,就是各种不舒服.那既然我们学习了生成器,就可以对其进行优化了

    function* foo() {
        let res = yield getScore(commonUrl + 'provinceID=p41')
        if(有该省) {
            let res1 = yield getScore(commonUrl + 'provinceID=p41&&cityID=ct03') 
            if(有该市) {
                let res2 = yield getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06')
                // ...
            }
        }
    }
    function autoExec(fn) {
        const generator = fn()
        function innerFn(res) {
            const result = generator.next(res)
            if(result.done) return result.value
            result.value.then(res => {
                innerFn(res)
            })           
        }
        innerFn()
    }
    autoExec(foo)

先获取生成器函数的生成器,然后调用next获取结果,如果迭代完成返回结果,如果没有,将结果作为参数传入next中...

    async function foo() {
        let res = await getScore(commonUrl + 'provinceID=p41')
        if(有该省) {
            let res1 = await getScore(commonUrl + 'provinceID=p41&&cityID=ct03') 
            if(有该市) {
                let res2 = await getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06')
                // ...
            }
        }
    }

async/await实际上是方法二的语法糖,只是将*替换为了async并修改了位置,将yield替换为await.

四、async/await异步函数

1.什么是异步函数

    async function demo() {
        console.log('我被执行了1')
        console.log('我被执行了2')
        console.log('我被执行了3')
    }
    demo()

普通函数的前面加上async,那么这个函数就可以称之为异步函数.和普通函数一样进行调用,代码会依次执行.

2.异步函数的执行过程

    console.log('aaaaaa')
    async function demo() {
        console.log('我被执行了1')
        console.log('我被执行了2')
        console.log('我被执行了3')
    }
    console.log('bbbbbbb')
    demo()
    console.log('ccccc')

image.png

如果异步函数没有其他特殊的代码,和普通的函数执行顺序是没有什么区别的.不要认为只要是函数前面有async,它就会延迟执行.

3.异步函数和普通函数的区别

额,写法的区别就不说了

3.1 返回值是一个Promise

    // 异步函数
    async function foo() {
        return 333 // ① res: 333
        return '哈哈哈' // ② res: 哈哈哈
        return {
            then: function(resolve, reject) {
                resolve('mmm')
            }
        } // ③ res: mmm
        return new Promise((resolve, reject) => {
            setTimeout(() => { resolve('嘿嘿嘿') }, 300)
        }) // ④ res: 嘿嘿嘿
    }
    const p = foo()
    p.then(res => {
        console.log('res:', res)
    }).catch(err => {
        console.log('err:', err)
    })
  • 异步函数的返回值是一个Promise
  • 函数return时,then方法就会被执行.并且return的结果就是Promise的结果

3.2 抛出异常不影响后续代码执行

    async function foo() {
        console.log('aaaa')
        throw new Error('未知错误')
        console.log('bbb')
        console.log('ccc')
    }
    foo().catch(err => {
        console.log('err1:', err)
    })
    console.log('哈哈哈')
    console.log('嘿嘿嘿')

image.png

  • 代码中抛出异常,会作为Promise的reject结果
  • 后续代码仍然可以运行,普通函数则会终止执行

3.3 await关键字

    async function foo() {
        console.log('aaa')
        const res = await 321 // ① bbb 321
        const res = await {
            then: function(resolve, reject) {
                resolve('mmm')
            }
        } // ② bbb mmm
        const res = await new Promise((resolve, reject) => {
            setTimeout(() => { resolve('嘿嘿嘿') }, 300) // ③ bbb 嘿嘿嘿
            setTimeout(() => { reject('哼哼哼') }, 300) // ④ err: 嘿嘿嘿
        }) 
        console.log('bbb:', res)
    }
    foo().catch(err => {
        console.log('err:', err)
    })
  • await只能和async搭配使用
  • await后面可跟普通值,也可跟thenable、Promise、表达式
  • 一旦reject时,reject的结果会作为异步函数返回的Promise的结果