Maison  >  Article  >  interface Web  >  Explication détaillée de l'exécution automatique de Generator dans ES6

Explication détaillée de l'exécution automatique de Generator dans ES6

不言
不言avant
2018-10-19 15:10:592162parcourir

Cet article vous apporte une explication détaillée de l'exécution automatique de Generator dans ES6. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Tâche asynchrone unique

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

function* gen(){
    var url = 'https://api.github.com/users/github';
    var result = yield fetch(url);
    console.log(result.bio);
}

Afin d'obtenir le résultat final de l'exécution, vous devez faire ceci :

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

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

Première exécution la fonction Générateur, Obtenez l'objet traverseur.

Utilisez ensuite la méthode suivante pour exécuter la première phase de la tâche asynchrone, qui est fetch(url).

Notez que puisque fetch(url) renverra un objet Promise, la valeur du résultat est :

{ value: Promise { <pending> }, done: false }

Enfin, nous ajoutons une méthode then à cet objet Promise et le renvoyons d'abord Formater le data (data.json()), puis appelez g.next pour transmettre les données obtenues, afin que la deuxième phase de la tâche asynchrone puisse être exécutée et que l'exécution du code soit terminée.

Tâches asynchrones multiples

Nous n'avons appelé qu'une seule interface dans la section précédente, donc si nous appelions plusieurs interfaces et utilisions plusieurs rendements, ne devrions-nous pas imbriquer continuellement dans la fonction then...

Jetons donc un coup d'œil à l'exécution de plusieurs tâches asynchrones :

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'));
}

Afin d'obtenir le résultat final de l'exécution, vous devrez peut-être écrire :

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)
});

Mais je sais que vous ne voulez certainement pas l'écrire comme ça...

En fait, en utilisant la récursivité, on peut écrire comme ceci :

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);

La clé est rendement Lorsqu'un objet Promise est renvoyé, ajoutez une méthode then à cet objet Promise et exécutez-le lorsque l'opération asynchrone réussit. La fonction onFullfilled exécute ensuite g.next dans la fonction onFullfilled, permettant ainsi au générateur de Continuez l'exécution, puis renvoyez une promesse, exécutez g.next en cas de succès, puis revenez...

Fonction de démarrage

Exécutez cette fonction de démarrage, nous formatons les données dans la fonction then data.json(), mais dans des cas plus généraux, par exemple, rendement est directement suivi d'une promesse, plutôt que d'une promesse renvoyée par la fonction fetch, car il n'y a pas de méthode json, le code signalera une erreur . Donc, afin d'être plus polyvalent, avec cet exemple et ce starter, nous l'avons modifié comme suit :

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);

Tant que le rendement est suivi d'un objet Promise, nous pouvons utiliser cette fonction d'exécution pour exécuter automatiquement le Fonction générateur.

Fonction de rappel

Le rendement doit-il être suivi d'un objet Promise pour assurer l'exécution automatique du Générateur ? Et si c'était juste une fonction de rappel ? Regardons un exemple :

On simule d'abord une requête asynchrone normale :

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

On transforme cette fonction en :

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

Pour cette fonction Générateur :

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'));
}

Si vous souhaitez obtenir le résultat final :

var g = gen();

var r1 = g.next();

r1.value(function(data) {
    var r2 = g.next(data);
    r2.value(function(data) {
        g.next(data);
    });
});

Si écrit ainsi, nous serons confrontés au même problème que dans la première section, c'est-à-dire lors de l'utilisation multiple Lors du rendement, le code sera imbriqué dans des boucles...

utilise également la récursivité, on peut donc le transformer en :

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

On voit que l'exécution automatique de la fonction Générateur nécessite un mécanisme, c'est-à-dire que lorsque l'opération asynchrone a un résultat, le droit d'exécution peut être automatiquement restitué.

Et il y a deux façons de procéder.

(1) Fonction de rappel. Encapsulez l'opération asynchrone, exposez la fonction de rappel et renvoyez les droits d'exécution dans la fonction de rappel.

(2) Objet de promesse. Enveloppez les opérations asynchrones dans des objets Promise et utilisez la méthode then pour renvoyer les droits d'exécution.

Dans chacune des deux méthodes, nous avons écrit une fonction de lancement d'exécution. Pouvons-nous combiner ces deux méthodes et écrire une fonction d'exécution générale ? Essayons-le :

// 第一版
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;

En fait, l'implémentation est très simple. Déterminez si result.value est une promesse. Si c'est le cas, ajoutez la fonction then. Sinon, exécutez-la directement.

return Promise

Nous avons écrit une belle fonction de démarrage qui prend en charge le rendement suivi d'une fonction de rappel ou d'un objet Promise.

Maintenant, il y a une question à laquelle réfléchir, c'est-à-dire comment obtenir la valeur de retour de la fonction Générateur ? Et si une erreur se produit dans la fonction Générateur, comme la récupération d'une interface inexistante, comment détecter cette erreur ?

C'est facile à imaginer Promesse, si cette fonction de démarrage renvoie une Promesse, nous pouvons alors l'ajouter à cet objet Promise fonction, lorsque toutes les opérations asynchrones sont exécutées avec succès, nous exécutons la fonction onFullfilled, et en cas d'échec, nous exécutons la fonction onRejected.

On écrit une version :

// 第二版
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;

Elle est très différente de la première version :

D'abord, on renvoie une Promise, quand result.done est vrai Quand, nous résolvons (result.value) la valeur. Si une erreur se produit lors de l'exécution et est détectée, nous rejetterons (e) la raison.

Deuxièmement, nous utiliserons thunkToPromise pour envelopper la fonction de rappel dans une promesse, puis ajouterons la fonction then uniformément. Il est à noter ici que dans la fonction thunkToPromise, on suit d'abord le principe d'erreur, ce qui signifie que lorsque l'on traite le cas de la fonction de rappel :

// 模拟数据请求
function fetchData(url) {
    return function(cb) {
        setTimeout(function() {
            cb(null, { status: 200, data: url })
        }, 1000)
    }
}

En cas de succès, le premier paramètre doit renvoyer null , indiquant qu'il n'y a aucune raison d'erreur.

Optimisation

Sur la base de la deuxième version, nous avons écrit le code pour qu'il soit plus concis et élégant. Le code final est le suivant :

// 第三版
function run(gen) {

    return new Promise(function(resolve, reject) {
        if (typeof gen == 'function') gen = gen();

        // 如果 gen 不是一个迭代器
        if (!gen || typeof gen.next !== 'function') return resolve(gen)

        onFulfilled();

        function onFulfilled(res) {
            var ret;
            try {
                ret = gen.next(res);
            } catch (e) {
                return reject(e);
            }
            next(ret);
        }

        function onRejected(err) {
            var ret;
            try {
                ret = gen.throw(err);
            } catch (e) {
                return reject(e);
            }
            next(ret);
        }

        function next(ret) {
            if (ret.done) return resolve(ret.value);
            var value = toPromise(ret.value);
            if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
            return onRejected(new TypeError('You may only yield a function, promise ' +
                'but the following object was passed: "' + String(ret.value) + '"'));
        }
    })
}

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;
<.>

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);

是不是特别的好用?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer