关于javascript:一步一步理解Generator函数的原理

作者:麦乐

起源:恒生LIGHT云社区

Generator函数根本用法

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
 
var hw = helloWorldGenerator();

Generator函数调用后生成的就是一个迭代器对象,能够通过调用迭代器的next办法,管制函数外部代码的执行。

hw.next()
// { value: 'hello', done: false }
 
hw.next()
// { value: 'world', done: false }
 
hw.next()
// { value: 'ending', done: true }
 
hw.next()
// { value: undefined, done: true }

Generator函数遇到yield,能够在生成器函数外部暂停代码的执行使其挂起。在可迭代对象上调用next()办法能够使代码从暂停的地位开始持续往下执行。

先来理解一下什么是可迭代对象?

可迭代对象

要成为可迭代 对象, 一个对象必须实现 <strong>@@iterator</strong> 办法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 拜访该属性:

[Symbol.iterator] 一个无参数的函数,其返回值为一个合乎迭代器协定的对象。

let someString = "hi";
typeof someString[Symbol.iterator];   
let iterator = someString[Symbol.iterator]();
iterator + "";                               // "[object String Iterator]"
 
iterator.next();         // { value: "h", done: false }
iterator.next();         // { value: "i", done: false }
iterator.next();         // { value: undefined, done: true }

再来看一下,挂起是怎么回?有一个新的名词“协程”。

什么是协程?

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个过程能够领有多个线程一样,一个线程能够领有多个协程。

协程不是被操作系统内核所治理的,而是齐全由程序所管制,也就是在用户态执行。这样带来的益处是性能大幅度的晋升,因为不会像线程切换那样耗费资源。

协程不是过程也不是线程,而是一个非凡的函数,这个函数能够在某个中央挂起,并且能够从新在挂起处外持续运行。所以说,协程与过程、线程相比并不是一个维度的概念。

Generator 函数原理

Generator 函数是协程在 ES6 的实现,最大特点就是能够交出函数的执行权(即暂停执行)。

总结下来就是:

  • 一个线程存在多个协程
  • Generator函数是协程在ES6的实现
  • Yield挂起协程(交给其它协程),next唤起协程

讲到这里,应该会对Generator函数有一个从新的意识吧。在理论的开发中,间接应用Generator函数的场景并不常见,因为它只能通过手动调用next办法实现函数外部代码的程序执行。如果设想很好是应用它,能够为Generator函数实现一个主动执行神器。

主动执行的Generator函数

能够依据g.next()的返回值{value: ”, done: false}中done的值让Generator函数递归自执行:

 function run(generator) {
            var g = generator();

            var next = function() {
                var obj = g.next()
                console.log(obj)
                if(!obj.done) {
                    next()
                }
            }
            next()
        }
        run(helloWorldGenerator)

这样写能实现自执行性能,然而 不能保障执行程序。模仿两个异步申请:

    function sleep1() {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log('sleep1')
                    resolve(1)
                }, 1000)
            })
        }
        function sleep2() {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log('sleep2')
                    resolve(2)
                }, 1000)
            })
        }

批改函数

function* helloWorldGenerator() {
            yield sleep1();
            console.log(1);
            yield sleep2();
            console.log(2);
        }

执行 run(helloWorldGenerator)看一下打印程序:

异步函数还是在同步代码执行完当前执行的,如果想要实现异步代码也能依照程序执行,能够对代码进一步优化:

        function run(generator) {
            var g = generator();

            var next = function() {
                var obj = g.next();
                console.log(obj)
                if(obj.done) return;
                // 如果yield前面返回值不是promise,能够应用Promise.resolve包裹一下,避免报错
                Promise.resolve(obj.value).then(() => {next()})
            }
            next()
        }

如果说sleep1是一个网络申请的话,在yield前面就能够拿到申请返回的数据,看起来像是一种更优雅的异步问题解决方案。

如果想要拿到sleep函数resolve的值,也是能够实现的。

// 批改函数 变量接管yield语句返回后果
function* helloWorldGenerator() {
            var a = yield sleep1();
            console.log(a);
            var b = yield sleep2();
            console.log(b);
        }
// g.next(v); 传递后果值
      function run(generator) {
            var g = generator();

            var next = function(v) {
                var obj = g.next(v);
                console.log(obj)
                if(obj.done) return;
                // 如果yield前面返回值不是promise,能够应用Promise.resolve包裹一下,避免报错
                Promise.resolve(obj.value).then((v) => {next(v)})
            }
            next()
        }

你会看到和下面一样的打印后果。

认真看下面实现形式跟async await很类似,实际上这就是async await的原理。

async await

async await实质上就是联合promise实现的一个自执行Generator函数。将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已

更具体的代码如下,感兴趣的同学能够深刻理解一下:

 // async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已
            // async函数返回一个 Promise 对象,能够应用then办法增加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作实现,再接着执行函数体内前面的语句
            // Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器和一般函数一样执行
            // async函数对 Generator 函数的改良:
            /*
            (1)内置执行器
            (2)更好的语义
            (3)更广的适用性
            (4)返回值是 Promise
            */
 
  
            // async 函数的实现原理,就是将 Generator 函数和主动执行器,包装在一个函数里。
            async function fn(args) {
                // ...
            }
            // 等同于 外面的await 换成 yield
            function fn(args) {
                return spawn(function*() {
                    // ...
                });
            }
  
            function spawn(genF) {
                return new Promise(function(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); // 这也是为什么 不应用try catch的话,异样异步申请前面的代码不再执行的起因,从这里不再持续调用nex()办法了。
                        }
                        Promise.resolve(next.value).then(function(v) {
                            step(function() {
                                return gen.next(v);
                            });
                        }, function(e) {
                            step(function() {
                                return gen.throw(e); // 这里能够解释为什么async 函数须要应用try catch来捕捉异样,生成器函数的throw,会让代码到catch外面
                            });
                        });
                    }
                    step(function() {
                        return gen.next(undefined);
                    });
                });
            }

想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?

恒生LIGHT云社区,由恒生电子搭建的金融科技业余社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。

扫描下方小程序二维码,退出咱们!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理