国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

JavaScript 中使用 Generator的方法

2019-11-19 14:37:38
字體:
供稿:網(wǎng)友

Generator 是一種非常強力的語法,但它的使用并不廣泛(參見下圖 twitter 上的調(diào)查!)。為什么這樣呢?相比于 async/await,它的使用更復雜,調(diào)試起來也不太容易(大多數(shù)情況又回到了從前),即使我們可以通過非常簡單的方式獲得類似體驗,但是人們一般會更喜歡 async/await。

 

然而,Generator 允許我們通過 yield 關鍵字遍歷我們自己的代碼!這是一種超級強大的語法,實際上,我們可以操縱執(zhí)行過程!從不太明顯的取消操作開始,讓我們先從同步操作開始吧。

我為文中提到的功能創(chuàng)建了一個代碼倉庫 ―― github.com/Bloomca/obs…

批處理 (或計劃)

執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象,那意味著通過它我們可以同步地遍歷。為什么我們想這么做?原因有可能是為了實現(xiàn)批處理。想象一下,我們需要下載 1000 個項目,并在表格中逐行的顯示它們(不要問我為什么,假設我們不使用框架)。雖然立刻展示它們沒有什么不好的,但有時這可能不是最好的解決方案 ―― 也許你的 MacBook Pro 可以輕松處理它,但普通人的電腦不能(更別說手機了)。所以,這意味著我們需要用某種方式延遲執(zhí)行。

請注意,這個例子是關于性能優(yōu)化,在你遇到這個問題之前,沒必要這樣做 ――過早優(yōu)化是萬惡之源!

// 最初的同步實現(xiàn)版本function renderItems(items) { for (item of items) { renderItem(item); }}// 函數(shù)將由我們的執(zhí)行器遍歷執(zhí)行// 實際上,我們可以用相同的同步方式來執(zhí)行它!function* renderItems(items) { // 我使用 for..of 遍歷方法來避免新函數(shù)的產(chǎn)生 for (item of items) { yield renderItem(item); }}

沒有什么區(qū)別是吧?那么,這里的區(qū)別在于,現(xiàn)在我們可以在不改變源代碼的情況下以不同方式運行這個函數(shù)。實際上,正如我之前提到的,沒有必要等待,我們可以同步執(zhí)行它。所以,來調(diào)整下我們的代碼。在每個 yield 后邊加一個 4 ms(JavaScript VM 中的一個心跳) 的延遲怎么樣?我們有 1000 個項目,渲染將需要 4 秒 ―― 還不錯,假設我想在 2 秒之內(nèi)渲染完畢,很容易想到的方法是每次渲染 2 個。突然使用 Promise 的解決方案將變得更加復雜 ―― 我們必須要傳遞另一個參數(shù):每次渲染的項目個數(shù)。通過我們的執(zhí)行器,我們?nèi)匀恍枰獋鬟f這個參數(shù),但好處是對我們的 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);  } } });}// 第一個參數(shù) ―― 每批處理多少個項目const items = [...];batchRunner(2, function*() { for (item of items) { yield renderItem(item); }});

正如你所看到的,我們可以輕松改變每批處理項目的個數(shù),不去考慮執(zhí)行器,回到正常的同步執(zhí)行方式 ―― 所有這些都不會影響我們的 renderItems 方法。

取消

我們來考慮下傳統(tǒng)的功能 ―― 取消。在我promises cancellation in general ( 譯文:如何取消你的 Promise? ) 這篇文章中已經(jīng)詳細談到了。所以我會使用其中一些代碼:

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

這里最好的部分是我們可以取消所有還沒來得及執(zhí)行的請求(也可以給我們的執(zhí)行器傳遞類似AbortController 的對象參數(shù),所以它甚至可以取消當前的請求!),而且我們沒有修改過自己業(yè)務邏輯中的一行的代碼。

暫停/恢復

另一個特殊的需求可能是暫停/恢復功能。你為什么想要這個功能?想象一下,我們渲染了 1000 行數(shù)據(jù),而且速度非常慢,我們希望給用戶提供暫停/恢復渲染的功能,這樣他們就可以停止所有的后臺工作讀取已經(jīng)下載的內(nèi)容了。讓我們開始吧!

// 實現(xiàn)渲染的方法還是一樣的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 };}

調(diào)用這個執(zhí)行器,可以給我們返回一個具有暫停/恢復功能的對象,所有這些都可以輕松得到,還是使用我們之前的業(yè)務代碼!所以,如果你有很多"沉重"的請求鏈,需要耗費很長時間,而你想給你的用戶提供暫停/恢復功能的話,你可以隨意在你的代碼中實現(xiàn)這個執(zhí)行器。

錯誤處理

我們有個神秘的 onRejected 調(diào)用,這是我們這部分談論的主題。如果我們使用正常的 async/await 或 Promise 鏈式寫法,我們將通過 try/catch 語句來進行錯誤處理,如果不添加大量的邏輯代碼就很難進行錯誤處理。通常情況下,如果我們需要以某種方式處理錯誤(比如重試),我們只是在 Promise 內(nèi)部進行處理,這將會回調(diào)自己,可能再次回到同樣的點。而且,這還不是一個通用的解決方案 ―― 可悲的是,在這里甚至 Generator 也不能幫助我們。我們發(fā)現(xiàn)了 Generator 的局限 ―― 雖然我們可以控制執(zhí)行流程,但不能移動 Generator 函數(shù)的主體;所以我們不能后退一步,重新執(zhí)行我們的命令。一個可行的解決方案是使用command pattern, 它告訴了我們 yield 結(jié)果的數(shù)據(jù)結(jié)構(gòu) ―― 應該是我們需要執(zhí)行此命令需要的所有信息,這樣我們就可以再次執(zhí)行它了。所以,我們的方法需要改為:

function* renderItems() { for (item of items) { // 我們需要將所有東西傳遞出去: // 方法, 內(nèi)容, 參數(shù) yield [renderItem, null, item]; }}

正如你所看到的,這使得我們不清楚發(fā)生了什么 ―― 所以,也許最好是寫一些 wrapWithRetry 方法,它會檢查 catch 代碼塊中的錯誤類型并再次嘗試。但是我們?nèi)匀豢梢宰鲆恍┎挥绊懳覀児δ艿氖虑椤@纾覀兛梢栽黾右粋€關于忽略錯誤的策略 ―― 在 async/await 中我們不得不使用 try/catch 包裝每個調(diào)用,或者添加空的 .catch(() => {}) 部分。有了 Generator,我們可以寫一個執(zhí)行器,忽略所有的錯誤。

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);  }  // 為了區(qū)分錯誤和正常的結(jié)果  // 我們用它來執(zhí)行  next(result); } function next({ done, value }) {  if (done) {  return resolve(value);  }  // 假設我們總是接收 Promise,所以不需要檢查類型  return value.then(onFulfilled, onRejected); } });}

關于 async/await

Async/await 是現(xiàn)在的首選語法(甚至 co 也談到了它 ),這也是未來。但是,Generator 也在 ECMAScript 標準內(nèi),這意味著為了使用它們,除了寫幾個工具函數(shù),你不需要任何東西。我試圖向你們展示一些不那么簡單的例子,這些實例的價值取決于你的看法。請記住,沒有那么多人熟悉 Generator,并且如果在整個代碼庫中只有一個地方使用它們,那么使用 Promise 可能會更容易一些 ―― 但是另一方面,通過 Generator 某些問題可以被優(yōu)雅和簡潔的處理。

總結(jié)

以上所述是小編給大家介紹的在 JavaScript 中使用 Generator的方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網(wǎng)網(wǎng)站的支持!

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 确山县| 石台县| 怀柔区| 犍为县| 盈江县| 兖州市| 三明市| 会东县| 汉沽区| 赞皇县| 诸暨市| 玉环县| 福建省| 徐州市| 永宁县| 克拉玛依市| 朝阳县| 丰城市| 巴彦县| 龙山县| 青阳县| 武宣县| 依兰县| 榕江县| 六安市| 当阳市| 工布江达县| 灌云县| 安达市| 辽源市| 西盟| 琼结县| 渭南市| 咸丰县| 孝昌县| 清河县| 巫溪县| 武威市| 上虞市| 平陆县| 准格尔旗|