Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Beispiele für die Verwendung von Generator in JavaScript

Detaillierte Beispiele für die Verwendung von Generator in JavaScript

小云云
小云云Original
2017-12-29 14:12:311486Durchsuche

In diesem Artikel erfahren Sie hauptsächlich, wie Sie Generator in JavaScript verwenden. Generator ist eine sehr leistungsstarke Syntax, die jedoch nicht weit verbreitet ist (siehe die Umfrage auf Twitter unten!). Warum ist das so? Im Vergleich zu async/await ist die Verwendung komplizierter und das Debuggen ist nicht einfach (die meisten Fälle gehören der Vergangenheit an). Obwohl wir auf sehr einfache Weise eine ähnliche Erfahrung erzielen können, bevorzugen die Leute im Allgemeinen async/await.

Generator ermöglicht uns jedoch, über das Schlüsselwort yield über unseren eigenen Code zu iterieren! Das ist eine superstarke Syntax und wir können die Ausführung tatsächlich manipulieren! Beginnen wir mit den weniger offensichtlichen Löschvorgängen und beginnen wir mit den Synchronisierungsvorgängen.

Ich habe ein Code-Repository für die im Artikel erwähnten Funktionen erstellt - github.com/Bloomca/obs…

Stapelverarbeitung (oder Plan)

Die Ausführung der Generatorfunktion wird ausgeführt Gibt ein Traverser-Objekt zurück, was bedeutet, dass wir es synchron durchlaufen können. Warum wollen wir das tun? Der Grund kann darin liegen, eine Stapelverarbeitung zu implementieren. Stellen Sie sich vor, wir müssen 1000 Elemente herunterladen und sie Zeile für Zeile in einer Tabelle anzeigen (fragen Sie mich nicht nach dem Grund, vorausgesetzt, wir verwenden kein Framework). Es ist zwar nichts Falsches daran, sie sofort zur Schau zu stellen, aber manchmal ist das vielleicht nicht die beste Lösung – vielleicht kommt Ihr MacBook Pro problemlos damit zurecht, der Computer eines Durchschnittsmenschen jedoch nicht (ganz zu schweigen von einem Telefon). Das bedeutet also, dass wir die Ausführung irgendwie verzögern müssen.

Bitte beachten Sie, dass es in diesem Beispiel um Leistungsoptimierung geht. Es besteht keine Notwendigkeit, dies zu tun, bis Sie auf dieses Problem stoßen – vorzeitige Optimierung ist die Wurzel allen Übels

// 最初的同步实现版本
function renderItems(items) {
 for (item of items) {
 renderItem(item);
 }
}
// 函数将由我们的执行器遍历执行
// 实际上,我们可以用相同的同步方式来执行它!
function* renderItems(items) {
 // 我使用 for..of 遍历方法来避免新函数的产生
 for (item of items) {
 yield renderItem(item);
 }
}

macht keinen Unterschied! Rechts? Der Unterschied besteht darin, dass wir diese Funktion jetzt anders ausführen können, ohne den Quellcode zu ändern. Wie ich bereits erwähnt habe, besteht eigentlich keine Notwendigkeit zu warten, wir können es synchron tun. Also, lasst uns unseren Code optimieren. Wie wäre es mit dem Hinzufügen einer Verzögerung von 4 ms (ein Herzschlag in der JavaScript-VM) nach jedem Yield? Wir haben 1000 Elemente und das Rendern dauert 4 Sekunden – nicht schlecht, wenn ich davon ausgehe, dass ich in 2 Sekunden rendern möchte, kann ich mir das einfacher vorstellen, indem ich zwei auf einmal rendere. Plötzlich wird die Lösung mit Promises komplizierter – wir müssen einen weiteren Parameter übergeben: die Anzahl der Elemente, die jedes Mal gerendert werden sollen. Über unseren Executor müssen wir diesen Parameter noch übergeben, aber der Vorteil ist, dass er absolut keine Auswirkungen auf unsere renderItems-Methode hat.

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

Wie Sie sehen, können wir die Anzahl der Elemente pro Stapel unabhängig vom Ausführenden problemlos ändern und zur normalen synchronen Ausführung zurückkehren – alles ohne Auswirkungen auf unsere renderItems-Methode.

Abbrechen

Betrachten wir die traditionelle Funktion – Abbrechen. Ich habe darüber ausführlich in meinem Artikel „Kündigung von Versprechen im Allgemeinen“ gesprochen (Übersetzung: Wie kündige ich mein Versprechen?). Deshalb verwende ich einen Teil dieses Codes:

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

Das Beste daran ist, dass wir alle Anfragen abbrechen können, die noch keine Chance zur Ausführung hatten (wir können auch einen Objektparameter wie AbortController übergeben an unseren Executor, sodass Sie die aktuelle Anfrage sogar abbrechen können), und wir haben keine einzige Codezeile in unserer Geschäftslogik geändert.

Pause/Fortsetzen

Ein weiterer besonderer Bedarf könnte die Pause/Fortsetzungsfunktion sein. Warum möchten Sie diese Funktion? Stellen Sie sich vor, wir rendern 1000 Datenzeilen und es ist sehr langsam. Wir möchten dem Benutzer die Möglichkeit geben, das Rendern anzuhalten/fortzusetzen, damit er die gesamte Hintergrundarbeit stoppen und lesen kann, was heruntergeladen wurde. Fangen wir an!

// 实现渲染的方法还是一样的
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
 };
}

Der Aufruf dieses Executors kann uns ein Objekt mit Pausen-/Fortsetzungsfunktionen zurückgeben, die alle leicht erhältlich sind, oder unseren vorherigen Geschäftscode verwenden! Wenn Sie also viele „schwere“ Anforderungsketten haben, die lange dauern, und Sie Ihren Benutzern eine Pause-/Fortsetzungsfunktion bieten möchten, können Sie diesen Executor gerne in Ihren Code implementieren.

Fehlerbehandlung

Wir haben einen mysteriösen onRejected-Aufruf, der das Thema dieses Teils ist. Wenn wir die normale Async/Await- oder Promise-Verkettung verwenden, behandeln wir Fehler durch Try/Catch-Anweisungen, was ohne das Hinzufügen von viel Logikcode schwierig zu handhaben ist. Wenn wir einen Fehler auf irgendeine Weise behandeln müssen (z. B. einen erneuten Versuch), tun wir dies normalerweise einfach innerhalb des Versprechens, das sich selbst zurückruft, möglicherweise noch einmal zum selben Punkt. Und das ist noch keine universelle Lösung – leider kann uns hier nicht einmal Generator helfen. Wir haben eine Einschränkung des Generators entdeckt: Obwohl wir den Ausführungsfluss steuern können, können wir den Hauptteil der Generatorfunktion nicht verschieben, sodass wir unseren Befehl nicht erneut ausführen können. Eine mögliche Lösung besteht darin, das Befehlsmuster zu verwenden, das uns die Datenstruktur des Ertragsergebnisses mitteilt – es sollte alle Informationen enthalten, die wir zum Ausführen dieses Befehls benötigen, damit wir ihn erneut ausführen können. Daher muss unsere Methode geändert werden in:

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(异步)函数的用法

JS异步实现Generator的方法

详解yield和Generators生成器

Das obige ist der detaillierte Inhalt vonDetaillierte Beispiele für die Verwendung von Generator in JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn