NodeJS系列(6)- ECMAScript 6 (ES6) 语法(四)

发布时间 2023-06-29 10:31:58作者: 垄山小站


本文在 “NodeJS系列(2)- NPM 项目 Import/Export ES6 模块” 的 npmdemo 项目的基础上,继续介绍并演示 Promise 对象、Generator 函数、async 函数 等 ES6 语法和概念。

NodeJS ES6:https://nodejs.org/en/docs/es6
ECMA:https://www.ecma-international.org/publications-and-standards/standards/ecma-262/

1. Promise 对象

    Promise 是异步编程的一种解决方案:从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息;从语义上讲,它是承诺,承诺它过一段时间会给你一个结果。Promise 有三种状态:pending (等待),fulfiled (成功),rejected (失败);状态一旦改变,就不会再变。Promise 实例创建后 (new 操作后),会立即执行。

    Promise 其实是一个构造函数,有 all、reject、resolve 这几个方法,原型上有 then、catch 等方法。

    使用 Promise 可以避免函数多级回调嵌套,提高代码的可读性,降低代码的维护难度,而且 Promise 可以支持多个并发的请求,获取并发请求中的数据。

    Promise 提供的 “状态” 机制,用维护状态、传递状态的方式来使得回调函数能够及时被调用,比传递 callback 函数要简单、灵活。
    
    示例代码如下:

        let p = new Promise((resolve, reject) => {
            console.log("promise -> start")
            setTimeout(function(){
                let num = Math.random()
                console.log("setTimeout: num = " + num)
                if (num >= 0.5) {
                    // 成功
                    resolve(num)
                } else {
                    // 失败
                    reject('< 0.5')
                }
            }, 2000)
            console.log("promise -> end")
        })

        p.then((data) => {
            // 成功
            console.log('resolve: ' + data)
        }).catch((err) => {
            // 失败
            console.log("reject: " + err)
        })

    输出成功结果:

        promise -> start
        promise -> end
        setTimeout: num = 0.6989372571259511
        resolve: 0.6989372571259511


    输出失败结果:

        promise -> start
        promise -> end
        setTimeout: num = 0.09101305143954774
        reject: < 0.5


    then 方法接收 resolve 返回的数据,catch 方法接收 reject 返回的数据。

    1) then 链式操作  

        有三个任务 task 1、task 2 和 task 3,task 2 必须收到 task 1 的成功结果才能执行,task 3 必须收到 task 2 的成功结果才能执行。

        以下是 then 链式操作的示例代码:

            function task2() {
                let p2 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 2 finish')
                        resolve('success_2')
                    }, 1000)
                })
                return p2
            }

            function task3() {
                var p3 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 3 finish')
                        resolve('success_3')
                    }, 1000)
                })
                return p3     
            }

            let p1 = new Promise((resolve, reject) => {
                setTimeout(function(){
                    console.log('Task 1 finish')
                    resolve('success_1')
                }, 1000)
            })

            p1.then((data) => {
                console.log(data)
                return task2()
            }).then((data) => {
                console.log(data)
                return task3()
            }).then((data) => {
                console.log(data)
                return 'success_4'
            }).then((data) => {
                console.log(data)
            })


        输出结果:

            Task 1 finish
            success_1
            Task 2 finish
            success_2
            Task 3 finish
            success_3
            success_4


        示例代码中 p1.then 和第 2 个 then 依次返回 p2 和 p3 两个 Promise 对象,即第 2 个 then 绑定在 p2 上,第 3 个 then 绑定在 p3 上。
        
        第 3 个 then 返回的是字符串(不是 Promise 对象),所以第 4 个 then 没有绑定 Promise 对象,直接接收第 3 个 then 的返回数据。
 
    2) all 方法

        all 方法是与 then 同级的并行执行异步操作的方法。all 方法是在所有异步操作执行完后,并且执行结果都是成功的时候才执行回调。

        以下是 all 方法的示例代码:

            function task1() {
                let p1 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 1')
                        resolve('success_1')
                    }, 1000)
                })
                return p1
            }

            function task2() {
                let p2 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 2')
                        resolve('success_2')
                    }, 1000)
                })
                return p2
            }

            function task3() {
                var p3 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 3')
                        resolve('success_3')
                        //reject('error_3')
                    }, 1000)
                })
                return p3     
            }

            console.log('Promise.all() ...')
            Promise.all([task1(), task2(), task3()])
                    .then((data) => {
                        console.log(data)
                    }).catch((err) => {
                        console.log(err)
                    })


        输出结果:

            Promise.all() ...
            Task 1
            Task 2
            Task 3
            [ 'success_1', 'success_2', 'success_3' ]


        以上 task1()、 task2() 和 task3() 函数都是调用 resovle 返回成功,满足 all 方法的条件,执行 then 方法 。
        
        修改 task3() 函数,注释掉 resolve('success_3') 行,去掉 //reject('error_3') 行的注释符,运行后 task3() 调用 reject 返回失败,不满足 all 方法的条件,执行 catch 方法。  

    3) race 方法

        race 方法是与 then 同级的并行执行异步操作的方法。race 方法是不管执行完成的状态是成功或失败,先执行完成的方法就先执行回调,其它方法的执行结果都被忽略。

        以下是 race 方法的示例代码:

            function task1() {
                let p1 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 1')
                        resolve('success_1')
                    }, 5000)
                })
                return p1
            }

            function task2() {
                let p2 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 2')
                        resolve('success_2')
                    }, 3000)
                })
                return p2
            }

            function task3() {
                var p3 = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        console.log('Task 3')
                        resolve('success_3')
                        //reject('error_3')
                    }, 1000)
                })
                return p3     
            }

            console.log('Promise.race() ...')
            Promise.race([task1(), task2(), task3()])
                    .then((data) => {
                        console.log(data)
                    }).catch((err) => {
                        console.log(err)
                    })

        输出结果:

            Promise.race() ...
            Task 3
            success_3
            Task 2
            Task 1


        以上 task3() 最先调用 resovle 返回成功,满足 race 方法的条件,执行 then 方法。task1() 和  task2() 继续执行,但是执行结果被忽略,即它们俩没有执行 then 方法。
        
        修改 task3() 函数,注释掉 resolve('success_3') 行,去掉 //reject('error_3') 行的注释符,运行后 task3() 最先调用 reject 返回失败,也满足 race 方法成功返回的条件,执行 then 方法。  


2. Generator 函数

    Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

    Generator 函数语法格式:
    
        function* f() {
            yield ...
        }

        注:* 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。
    
    示例

        function* test() {
            yield 1
            yield 2
            return 3
        }

        let t = test()   // 创建遍历器(或迭代器)
        let r = t.next()

        while(!r.done) {
            console.log(r)
            r = t.next()

        }

        console.log(r)

    输出结果:

        { value: 1, done: false }
        { value: 2, done: false }
        { value: 3, done: true }

    遍历器调用 next() 方法,遇到 yield 停止执行并返回对象 { value: 1, done: false },done 的值为 true 时,表示遍历已经结束。
    
    1) next() 方法的参数

        next() 方法返回一个对象,yield 语句本身是没有返回值,或者说返回 undefined。next() 方法可以带一个参数,该参数就会被当作上一个 yield 语句的返回值。

        示例

            function* gen() {
                for (let i=0; true; i++) {
                    console.log('yield ' + i);
                    let y = yield i;
                    console.log('y = ' + y);
                    
                    if (y) {
                        return i+1;
                    }
                }
            }

            var g = gen();
            console.log(g.next())
            console.log(g.next())
            console.log(g.next(true))


        输出结果:

            yield 0
            { value: 0, done: false }
            y = undefined
            yield 1
            { value: 1, done: false }
            y = true
            { value: 2, done: true }


        通过 next() 方法的参数,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

        由于 next() 方法的参数表示上一个 yield 语句的返回值,所以第一次使用 next() 方法时,不能带有参数,V8 引擎直接忽略第一次使用 next() 方法时的参数。

    2) for ... of 遍历

        for ... of 循环可以遍历 Generator 函数,不再需要调用 next() 方法,遍历到返回对象的 done 属性为 true,循环就会中止,且不包含该返回对象。

        示例

            function* f() {
                yield 1;
                yield 2;
                yield 3;
                return 4;
            }

            for (let i of f()) {
                console.log(i);
            }

        输出结果:

            1
            2
            3
    
    3) yield* 语句

        如果 yield 命令后面是一个遍历器,需要在 yield 命令后面加上星号,表明它返回的是一个遍历器,即 yield* 语句的返回值。

        示例

           let a = (function* () {
                yield 'Hello';
                yield 'Bye';
            } ());

            let b = (function* () {
                yield 'Good';
                yield* a;
                yield 'OK';
            } ());

            for (let i of b) {i
                console.log(i);
            }


        输出结果:

            Good
            Hello
            Bye
            OK


        如果 yield* 语句后面是可遍历(或可迭代)类型的数据(比如:数组),表明它返回的是这个可遍历数据的遍历器。

        示例

            // 遍历数组
            function* arr(){
                yield* ["a", "b"];
            }
            
            for (let i of arr()) {
                console.log(i);
            }

            console.log('-------------------------')

            // 遍历嵌套数组
            function* iterArr(marr) {
                if (Array.isArray(marr)) {
                    for(let i=0; i < marr.length; i++) {
                        yield* iterArr(marr[i]);
                    }
                } else {
                    yield marr;
                }
            }

            const marr = [ 1, [2, 3], 4 ];
            for(let i of iterArr(marr)) {
                console.log(i);
            }


        输出结果:

            a
            b
            -------------------------
            1
            2
            3
            4



3. async 函数

    async/await 是 ES7 才有的与异步操作有关的关键字,和 Promise,Generator 有很大关联的。

    async 函数是 Generator 函数的改进版,在写法上比较相似。通过 await 操作符可以让异步操作等待,直至执行完毕为止。同时 await 还会对等待的代码进行检测,如果不是异步,将不会执行等待。
    
    示例

        function task1(msg) {
            let p = new Promise(function(resolve, reject){
                setTimeout(function(){
                    resolve(msg)
                }, 2000)
            })
            return p
        }

        function task2(msg) {
            let p = new Promise(function(resolve, reject){
                setTimeout(function(){
                    reject('error_2')
                }, 2000)
            })
            return p
        }

        async function main() {

            try {

                console.log("Start 1")
                let ret1 = await task1('task 1')
                console.log("await ret1: " + ret1)

                console.log("Start 2 ")
                let ret2 = await task2('task 2')
                console.log("await ret2: " + ret2)

            } catch (err) {
                console.log(err)
            }
        }

        main()


    输出结果:

        Start 1
        await ret1: task 1
        Start 2
        error_2


    以上输出结果可以看出,加 await 就可以实现异步操作同步化,而且要增加某种判断。所以 async/await 相对于 Generator 函数显得简单很多。