ES6におけるGeneratorの自動実行について詳しく解説

不言
リリース: 2018-10-19 15:10:59
転載
2164 人が閲覧しました

この記事では、ES6 でのジェネレーターの自動実行について詳しく説明します。必要な方は参考にしていただければ幸いです。

単一の非同期タスク

var fetch = require('node-fetch');

function* gen(){
    var url = 'https://api.github.com/users/github';
    var result = yield fetch(url);
    console.log(result.bio);
}
ログイン後にコピー

最終的な実行結果を取得するには、これを行う必要があります:

var g = gen();
var result = g.next();

result.value.then(function(data){
    return data.json();
}).then(function(data){
    g.next(data);
});
ログイン後にコピー

最初にジェネレーター関数を実行し、トラバーサー オブジェクトを取得します。

次に、次のメソッドを使用して、非同期タスクの最初のフェーズである fetch(url) を実行します。

fetch(url) は Promise オブジェクトを返すため、result の値は次のようになります:

{ value: Promise { <pending> }, done: false }
ログイン後にコピー

最後に、この Promise オブジェクトに then メソッドを追加し、最初に返されるデータをフォーマットします。 . (data.json()) を呼び出し、取得したデータを渡すために g.next を呼び出します。これにより、非同期タスクの 2 番目のフェーズが実行され、コードの実行が完了します。

複数の非同期タスク

前のセクションでは 1 つのインターフェイスだけを呼び出したので、複数のインターフェイスを呼び出して複数の yield を使用した場合、継続的にネストする必要があるのではないでしょうか。 then 関数内...

それでは、複数の非同期タスクの実行を見てみましょう:

var fetch = require('node-fetch');

function* gen() {
    var r1 = yield fetch('https://api.github.com/users/github');
    var r2 = yield fetch('https://api.github.com/users/github/followers');
    var r3 = yield fetch('https://api.github.com/users/github/repos');

    console.log([r1.bio, r2[0].login, r3[0].full_name].join('\n'));
}
ログイン後にコピー

最終的な実行結果を取得するには、次のように記述する必要がある場合があります:

var g = gen();
var result1 = g.next();

result1.value.then(function(data){
    return data.json();
})
.then(function(data){
    return g.next(data).value;
})
.then(function(data){
    return data.json();
})
.then(function(data){
    return g.next(data).value
})
.then(function(data){
    return data.json();
})
.then(function(data){
    g.next(data)
});
ログイン後にコピー

しかし、このように書きたくないのはわかっています...

実際、再帰を使用すると、次のように書くことができます:

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

    function next(data) {
        var result = g.next(data);

        if (result.done) return;

        result.value.then(function(data) {
            return data.json();
        }).then(function(data) {
            next(data);
        });

    }

    next();
}

run(gen);
ログイン後にコピー

重要なのはyield 時に Promise オブジェクトを返すには、この Promise オブジェクトに then メソッドを追加し、非同期操作が成功したときに実行します。 次に、onFullfilled 関数が onFullfilled 関数内で g.next を実行するため、ジェネレーターは次のことを行うことができます。 実行を継続し、Promise を返し、成功したら g.next を実行して戻ります...

スターター関数

このスターター関数を実行し、データをフォーマットしますthen 関数 data.json() 内ですが、より一般的な場合、たとえば、fetch 関数によって返される Promise ではなく、yield の後に Promise が直接続きます。json メソッドがないため、コードはエラーを報告します。 。したがって、より汎用性を高めるために、この例とスターターを合わせて、次のように変更しました。

var fetch = require('node-fetch');

function* gen() {
    var r1 = yield fetch('https://api.github.com/users/github');
    var json1 = yield r1.json();
    var r2 = yield fetch('https://api.github.com/users/github/followers');
    var json2 = yield r2.json();
    var r3 = yield fetch('https://api.github.com/users/github/repos');
    var json3 = yield r3.json();

    console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}

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

    function next(data) {
        var result = g.next(data);

        if (result.done) return;

        result.value.then(function(data) {
            next(data);
        });

    }

    next();
}

run(gen);
ログイン後にコピー

yield の後に Promise オブジェクトが続く限り、この run 関数を使用して Generator 関数を自動的に実行できます。 。

コールバック関数

ジェネレーターを確実に自動実行するには、yield の後に Promise オブジェクトを続ける必要がありますか?それが単なるコールバック関数の場合はどうなるでしょうか?例を見てみましょう:

まず、通常の非同期リクエストをシミュレートします:

function fetchData(url, cb) {
    setTimeout(function(){
        cb({status: 200, data: url})
    }, 1000)
}
ログイン後にコピー

この関数を次のように変換します:

function fetchData(url) {
    return function(cb){
        setTimeout(function(){
            cb({status: 200, data: url})
        }, 1000)
    }
}
ログイン後にコピー

そのようなジェネレーター関数の場合:

function* gen() {
    var r1 = yield fetchData('https://api.github.com/users/github');
    var r2 = yield fetchData('https://api.github.com/users/github/followers');

    console.log([r1.data, r2.data].join('\n'));
}
ログイン後にコピー

最終結果を取得したい場合:

var g = gen();

var r1 = g.next();

r1.value(function(data) {
    var r2 = g.next(data);
    r2.value(function(data) {
        g.next(data);
    });
});
ログイン後にコピー

このように書くと、最初のセクションと同じ問題に直面します。つまり、複数の yield を使用する場合、コードは次のように埋め込まれます。ループ それをまとめると...

も再帰を使用するので、次のように変換できます:

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

    function next(data) {
        var result = g.next(data);

        if (result.done) return;

        result.value(next);
    }

    next();
}

run(gen);
ログイン後にコピー

run

これからわか​​るようにジェネレーター機能 自動実行には、非同期操作の結果が得られたときに実行権限を自動的に引き渡すことができるメカニズムが必要です。

そして、これを行うには 2 つの方法があります。

(1) コールバック関数。非同期操作をラップし、コールバック関数を公開し、コールバック関数の実行権限を返します。

(2) Promise オブジェクト。非同期操作を Promise オブジェクトにラップし、 then メソッドを使用して実行権限を返します。

2 つのメソッドのそれぞれで、実行ランチャー関数を作成しました。これら 2 つのメソッドを組み合わせて、一般的な実行関数を作成できますか?試してみましょう:

// 第一版
function run(gen) {
    var gen = gen();

    function next(data) {
        var result = gen.next(data);
        if (result.done) return;

        if (isPromise(result.value)) {
            result.value.then(function(data) {
                next(data);
            });
        } else {
            result.value(next)
        }
    }

    next()
}

function isPromise(obj) {
    return 'function' == typeof obj.then;
}

module.exports = run;
ログイン後にコピー

実際、実装は非常に簡単です。result.value が Promise であるかどうかを判断し、そうでない場合は then 関数を追加します。

return Promise

yield に続いてコールバック関数または Promise オブジェクトをサポートする優れたスターター関数を作成しました。

ここで考えるべき問題があります。それは、ジェネレーター関数の戻り値をどのように取得するかということです。また、存在しないインターフェイスをフェッチするなど、ジェネレーター関数でエラーが発生した場合、このエラーをどのようにキャッチすればよいでしょうか?

これは簡単に考えられます Promise、このスターター関数が Promise を返す場合、この Promise オブジェクトに then を追加できます。 関数では、すべての非同期操作が正常に実行された場合は onFullfilled 関数を実行し、失敗した場合は onRejected 関数を実行します。

バージョンを作成します:

// 第二版
function run(gen) {
    var gen = gen();

    return new Promise(function(resolve, reject) {

        function next(data) {
            try {
                var result = gen.next(data);
            } catch (e) {
                return reject(e);
            }

            if (result.done) {
                return resolve(result.value)
            };

            var value = toPromise(result.value);

            value.then(function(data) {
                next(data);
            }, function(e) {
                reject(e)
            });
        }

        next()
    })

}

function isPromise(obj) {
    return 'function' == typeof obj.then;
}

function toPromise(obj) {
    if (isPromise(obj)) return obj;
    if ('function' == typeof obj) return thunkToPromise(obj);
    return obj;
}

function thunkToPromise(fn) {
    return new Promise(function(resolve, reject) {
        fn(function(err, res) {
            if (err) return reject(err);
            resolve(res);
        });
    });
}

module.exports = run;
ログイン後にコピー

最初のバージョンとは大きく異なります:

まず、Promise を返します。 result.done が true の場合、解決します。 (result.value) 値。実行中にエラーが発生して捕捉された場合、その理由を拒否します。

2 番目に、thunkToPromise を使用してコールバック関数を Promise にラップし、then 関数を均一に追加します。ここで、 thunkToPromise 関数では、エラー第一原則に従っていることに注目してください。これは、コールバック関数の状況に対処するときを意味します。

// 模拟数据请求
function fetchData(url) {
    return function(cb) {
        setTimeout(function() {
            cb(null, { status: 200, data: url })
        }, 1000)
    }
}
ログイン後にコピー

成功すると、最初のパラメーターは null を返し、次のエラーがあることを示します。エラーの原因はありません。

最適化

2 番目のバージョンに基づいて、より簡潔かつエレガントにコードを作成しました。最終的なコードは次のとおりです。 #co

如果我们再将这个启动器函数写的完善一些,我们就相当于写了一个 co,实际上,上面的代码确实是来自于 co……

而 co 是什么? co 是大神 TJ Holowaychuk 于 2013 年 6 月发布的一个小模块,用于 Generator 函数的自动执行。

如果直接使用 co 模块,这两种不同的例子可以简写为:

// yield 后是一个 Promise
var fetch = require('node-fetch');
var co = require('co');

function* gen() {
    var r1 = yield fetch('https://api.github.com/users/github');
    var json1 = yield r1.json();
    var r2 = yield fetch('https://api.github.com/users/github/followers');
    var json2 = yield r2.json();
    var r3 = yield fetch('https://api.github.com/users/github/repos');
    var json3 = yield r3.json();

    console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}

co(gen);
ログイン後にコピー
// yield 后是一个回调函数
var co = require('co');

function fetchData(url) {
    return function(cb) {
        setTimeout(function() {
            cb(null, { status: 200, data: url })
        }, 1000)
    }
}

function* gen() {
    var r1 = yield fetchData('https://api.github.com/users/github');
    var r2 = yield fetchData('https://api.github.com/users/github/followers');

    console.log([r1.data, r2.data].join('\n'));
}

co(gen);
ログイン後にコピー

是不是特别的好用?

以上がES6におけるGeneratorの自動実行について詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート