Maison > Article > interface Web > Exemples détaillés d'utilisation de Generator en JavaScript
Cet article vous explique principalement comment utiliser Generator en JavaScript. Generator est une syntaxe très puissante, mais elle n'est pas largement utilisée (voir l'enquête sur twitter ci-dessous !). Pourquoi est-ce ainsi ? Par rapport à async/await, son utilisation est plus compliquée et le débogage n'est pas facile (la plupart des cas reviennent au passé. Même si nous pouvons obtenir une expérience similaire de manière très simple, les gens préfèrent généralement async/await .
Cependant, Generator nous permet de parcourir notre propre code via le mot-clé rendement ! C'est une syntaxe super puissante et nous pouvons réellement manipuler l'exécution ! En commençant par l’opération d’annulation la moins évidente, commençons par l’opération de synchronisation.
J'ai créé un référentiel de code pour les fonctions mentionnées dans l'article - github.com/Bloomca/obs…
Le traitement par lots (ou plan)
L'exécution de la fonction Générateur Renvoie un objet traverseur, ce qui signifie que nous pouvons le parcourir de manière synchrone. Pourquoi voulons-nous faire cela ? La raison peut être la mise en œuvre d'un traitement par lots. Imaginez que nous devions télécharger 1 000 éléments et les afficher ligne par ligne dans un tableau (ne me demandez pas pourquoi, en supposant que nous n'utilisons pas de framework). Bien qu'il n'y ait rien de mal à les montrer tout de suite, ce n'est parfois pas la meilleure solution - peut-être que votre MacBook Pro peut le gérer facilement, mais l'ordinateur d'une personne moyenne ne le peut pas (et encore moins un téléphone). Cela signifie donc que nous devons retarder l’exécution d’une manière ou d’une autre.
Veuillez noter que cet exemple concerne l'optimisation des performances, il n'est pas nécessaire de le faire jusqu'à ce que vous rencontriez ce problème - une optimisation prématurée est la racine de tous les maux
// 最初的同步实现版本 function renderItems(items) { for (item of items) { renderItem(item); } } // 函数将由我们的执行器遍历执行 // 实际上,我们可以用相同的同步方式来执行它! function* renderItems(items) { // 我使用 for..of 遍历方法来避免新函数的产生 for (item of items) { yield renderItem(item); } }
ne fait aucune différence ! Droite? Eh bien, la différence ici est que nous pouvons désormais exécuter cette fonction différemment sans changer le code source. En fait, comme je l’ai déjà mentionné, il n’est pas nécessaire d’attendre, nous pouvons le faire de manière synchrone. Alors, modifions notre code. Que diriez-vous d'ajouter un délai de 4 ms (un battement de cœur dans la VM JavaScript) après chaque rendement ? Nous avons 1000 éléments et le rendu prendra 4 secondes - pas mal, en supposant que je veuille effectuer un rendu en 2 secondes, la façon la plus simple d'y penser est d'en rendre 2 à la fois. Du coup, la solution utilisant Promises devient plus compliquée - nous devons passer un autre paramètre : le nombre d'éléments à restituer à chaque fois. Via notre exécuteur, nous devons toujours passer ce paramètre, mais l'avantage est qu'il n'a absolument aucun effet sur notre méthode renderItems.
function runWithBatch(chunk, fn, ...args) { const gen = fn(...args); let num = 0; return new Promise((resolve, promiseReject) => { callNextStep(); function callNextStep(res) { let result; try { result = gen.next(res); } catch (e) { return reject(e); } next(result); } function next({ done, value }) { if (done) { return resolve(value); } // every chunk we sleep for a tick if (num++ % chunk === 0) { return sleep(4).then(proceed); } else { return proceed(); } function proceed() { return callNextStep(value); } } }); } // 第一个参数 —— 每批处理多少个项目 const items = [...]; batchRunner(2, function*() { for (item of items) { yield renderItem(item); } });
Comme vous pouvez le constater, nous pouvons facilement modifier le nombre d'éléments par lot, quel que soit l'exécuteur, et revenir à une exécution synchrone normale - le tout sans affecter la méthode renderItems.
Annuler
Considérons la fonction traditionnelle - Annuler. J'en ai parlé en détail dans mon article Annulation des promesses en général (Traduction : Comment annuler sa Promesse ?). Je vais donc utiliser une partie de ce code :
function runWithCancel(fn, ...args) { const gen = fn(...args); let cancelled, cancel; const promise = new Promise((resolve, promiseReject) => { // define cancel function to return it from our fn // 定义 cancel 方法,并返回它 cancel = () => { cancelled = true; reject({ reason: 'cancelled' }); }; onFulfilled(); function onFulfilled(res) { if (!cancelled) { let result; try { result = gen.next(res); } catch (e) { return reject(e); } next(result); return null; } } function onRejected(err) { var result; try { result = gen.throw(err); } catch (e) { return reject(e); } next(result); } function next({ done, value }) { if (done) { return resolve(value); } // 假设我们总是接收 Promise,所以不需要检查类型 return value.then(onFulfilled, onRejected); } }); return { promise, cancel }; }
La meilleure partie ici est que nous pouvons annuler toutes les requêtes qui n'ont pas encore eu la chance de s'exécuter (nous pouvons également passer un paramètre d'objet comme AbortController à notre exécuteur, vous pouvez donc même annuler la requête en cours ! ), et nous n’avons pas modifié une seule ligne de code dans notre logique métier.
Pause/Reprise
Un autre besoin particulier pourrait être la fonctionnalité pause/reprise. Pourquoi voulez-vous cette fonctionnalité ? Imaginez que nous rendions 1 000 lignes de données, et que c'est très lent, et que nous voulons donner à l'utilisateur la possibilité de suspendre/reprendre le rendu afin qu'il puisse arrêter tout le travail en arrière-plan et lire ce qui a été téléchargé. Commençons !
// 实现渲染的方法还是一样的 function* renderItems() { for (item of items) { yield renderItem(item); } } function runWithPause(genFn, ...args) { let pausePromiseResolve = null; let pausePromise; const gen = genFn(...args); const promise = new Promise((resolve, reject) => { onFulfilledWithPromise(); function onFulfilledWithPromise(res) { if (pausePromise) { pausePromise.then(() => onFulfilled(res)); } else { onFulfilled(res); } } function onFulfilled(res) { let result; try { result = gen.next(res); } catch (e) { return reject(e); } next(result); return null; } function onRejected(err) { var result; try { result = gen.throw(err); } catch (e) { return reject(e); } next(result); } function next({ done, value }) { if (done) { return resolve(value); } // 假设我们总是接收 Promise,所以不需要检查类型 return value.then(onFulfilledWithPromise, onRejected); } }); return { pause: () => { pausePromise = new Promise(resolve => { pausePromiseResolve = resolve; }); }, resume: () => { pausePromiseResolve(); pausePromise = null; }, promise }; }
Appeler cet exécuteur peut nous renvoyer un objet avec des fonctions pause/reprise, qui peuvent toutes être facilement obtenues, ou utiliser notre code métier précédent ! Donc, si vous avez beaucoup de chaînes de requêtes « lourdes » qui prennent beaucoup de temps et que vous souhaitez fournir une fonctionnalité pause/reprise à vos utilisateurs, n'hésitez pas à implémenter cet exécuteur dans votre code.
Gestion des erreurs
Nous avons un mystérieux appel onRejected, qui est le sujet de cette partie. Si nous utilisons un chaînage async/await ou Promise normal, nous gérerons les erreurs via des instructions try/catch, ce qui est difficile à gérer sans ajouter beaucoup de code logique. Normalement, si nous devons gérer une erreur d'une manière ou d'une autre (comme réessayer), nous le faisons simplement dans la promesse, qui se rappellera sur elle-même, éventuellement pour revenir au même point. Et ce n’est pas encore une solution universelle – malheureusement, même Generator ne peut pas nous aider ici. Nous avons découvert une limitation de Generator : bien que nous puissions contrôler le flux d'exécution, nous ne pouvons pas déplacer le corps de la fonction Generator, nous ne pouvons donc pas revenir en arrière et réexécuter notre commande ; Une solution possible consiste à utiliser le modèle de commande, qui nous indique la structure des données du résultat du rendement - devrait contenir toutes les informations dont nous avons besoin pour exécuter cette commande afin que nous puissions l'exécuter à nouveau. Par conséquent, notre méthode doit être modifiée comme suit :
function* renderItems() { for (item of items) { // 我们需要将所有东西传递出去: // 方法, 内容, 参数 yield [renderItem, null, item]; } }
正如你所看到的,这使得我们不清楚发生了什么 —— 所以,也许最好是写一些 wrapWithRetry 方法,它会检查 catch 代码块中的错误类型并再次尝试。但是我们仍然可以做一些不影响我们功能的事情。例如,我们可以增加一个关于忽略错误的策略 —— 在 async/await 中我们不得不使用 try/catch 包装每个调用,或者添加空的 .catch(() => {}) 部分。有了 Generator,我们可以写一个执行器,忽略所有的错误。
function runWithIgnore(fn, ...args) { const gen = fn(...args); return new Promise((resolve, promiseReject) => { onFulfilled(); function onFulfilled(res) { proceed({ data: res }); } // 这些是 yield 返回的错误 // 我们想忽略它们 // 所以我们像往常一样做,但不去传递出错误 function onRejected(error) { proceed({ error }); } function proceed(data) { let result; try { result = gen.next(data); } catch (e) { // 这些错误是同步错误(比如 TypeError 等) return reject(e); } // 为了区分错误和正常的结果 // 我们用它来执行 next(result); } function next({ done, value }) { if (done) { return resolve(value); } // 假设我们总是接收 Promise,所以不需要检查类型 return value.then(onFulfilled, onRejected); } }); }
关于 async/await
Async/await 是现在的首选语法(甚至 co 也谈到了它 ),这也是未来。但是,Generator 也在 ECMAScript 标准内,这意味着为了使用它们,除了写几个工具函数,你不需要任何东西。我试图向你们展示一些不那么简单的例子,这些实例的价值取决于你的看法。请记住,没有那么多人熟悉 Generator,并且如果在整个代码库中只有一个地方使用它们,那么使用 Promise 可能会更容易一些 —— 但是另一方面,通过 Generator 某些问题可以被优雅和简洁的处理。
相关推荐:
Promise,Generator(生成器),async(异步)函数的用法
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!