Maison  >  Article  >  interface Web  >  Comment utiliser les méthodes Generator en JavaScript

Comment utiliser les méthodes Generator en JavaScript

亚连
亚连original
2018-06-14 16:08:541438parcourir

Generator est une syntaxe très puissante, mais elle n'est pas largement utilisée. Cet article présente principalement comment utiliser Generator en JavaScript. Les amis qui en ont besoin peuvent se référer à

Generator est une syntaxe très puissante, mais son utilisation 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 les opérations d'annulation les moins évidentes, commençons par les opérations de synchronisation.

J'ai créé un référentiel de code pour les fonctionnalités mentionnées dans l'article - github.com/Bloomca/obs…

Batch (ou Schedule)

L'exécution de la fonction Générateur renverra 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 peut ê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

我们有个神秘的 onRejected 调用,这是我们这部分谈论的主题。如果我们使用正常的 async/await 或 Promise 链式写法,我们将通过 try/catch 语句来进行错误处理,如果不添加大量的逻辑代码就很难进行错误处理。通常情况下,如果我们需要以某种方式处理错误(比如重试),我们只是在 Promise 内部进行处理,这将会回调自己,可能再次回到同样的点。而且,这还不是一个通用的解决方案 —— 可悲的是,在这里甚至 Generator 也不能帮助我们。我们发现了 Generator 的局限 —— 虽然我们可以控制执行流程,但不能移动 Generator 函数的主体;所以我们不能后退一步,重新执行我们的命令。一个可行的解决方案是使用command pattern, 它告诉了我们 yield 结果的数据结构 —— 应该是我们需要执行此命令需要的所有信息,这样我们就可以再次执行它了。所以,我们的方法需要改为:

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 某些问题可以被优雅和简洁的处理。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在JS中实现点击下拉菜单内容同步输入框

在vue中使用cli如何实现重构多页面脚手架

在JS中如何实现通过拖拽改变物体大小

在JS中如何改变单物体透明度

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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn