亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript 中使用 Generator的方法

 更新時(shí)間:2017年12月29日 09:25:07   作者:jonjia  
Generator 是一種非常強(qiáng)力的語(yǔ)法,但它的使用并不廣泛。這篇文章主要介紹了如何在 JavaScript 中使用 Generator,需要的朋友可以參考下

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

 

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

我為文中提到的功能創(chuàng)建了一個(gè)代碼倉(cāng)庫(kù) —— github.com/Bloomca/obs…

批處理 (或計(jì)劃)

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

請(qǐng)注意,這個(gè)例子是關(guān)于性能優(yōu)化,在你遇到這個(gè)問(wèn)題之前,沒(méi)必要這樣做 ——過(guò)早優(yōu)化是萬(wàn)惡之源!

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

沒(méi)有什么區(qū)別是吧?那么,這里的區(qū)別在于,現(xiàn)在我們可以在不改變?cè)创a的情況下以不同方式運(yùn)行這個(gè)函數(shù)。實(shí)際上,正如我之前提到的,沒(méi)有必要等待,我們可以同步執(zhí)行它。所以,來(lái)調(diào)整下我們的代碼。在每個(gè) yield 后邊加一個(gè) 4 ms(JavaScript VM 中的一個(gè)心跳) 的延遲怎么樣?我們有 1000 個(gè)項(xiàng)目,渲染將需要 4 秒 —— 還不錯(cuò),假設(shè)我想在 2 秒之內(nèi)渲染完畢,很容易想到的方法是每次渲染 2 個(gè)。突然使用 Promise 的解決方案將變得更加復(fù)雜 —— 我們必須要傳遞另一個(gè)參數(shù):每次渲染的項(xiàng)目個(gè)數(shù)。通過(guò)我們的執(zhí)行器,我們?nèi)匀恍枰獋鬟f這個(gè)參數(shù),但好處是對(duì)我們的 renderItems 方法完全沒(méi)有影響。

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);
  }
 }
 });
}
// 第一個(gè)參數(shù) —— 每批處理多少個(gè)項(xiàng)目
const items = [...];
batchRunner(2, function*() {
 for (item of items) {
 yield renderItem(item);
 }
});

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

取消

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

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);
  }
  // 假設(shè)我們總是接收 Promise,所以不需要檢查類(lèi)型
  return value.then(onFulfilled, onRejected);
 }
 });
 return { promise, cancel };
}

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

暫停/恢復(fù)

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

// 實(shí)現(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);
  }
  // 假設(shè)我們總是接收 Promise,所以不需要檢查類(lèi)型
  return value.then(onFulfilledWithPromise, onRejected);
 }
 });
 return {
 pause: () => {
  pausePromise = new Promise(resolve => {
  pausePromiseResolve = resolve;
  });
 },
 resume: () => {
  pausePromiseResolve();
  pausePromise = null;
 },
 promise
 };
}

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

錯(cuò)誤處理

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

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

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

function runWithIgnore(fn, ...args) {
 const gen = fn(...args);
 return new Promise((resolve, promiseReject) => {
 onFulfilled();
 function onFulfilled(res) {
  proceed({ data: res });
 }
 // 這些是 yield 返回的錯(cuò)誤
 // 我們想忽略它們
 // 所以我們像往常一樣做,但不去傳遞出錯(cuò)誤
 function onRejected(error) {
  proceed({ error });
 }
 function proceed(data) {
  let result;
  try {
  result = gen.next(data);
  } catch (e) {
  // 這些錯(cuò)誤是同步錯(cuò)誤(比如 TypeError 等)
  return reject(e);
  }
  // 為了區(qū)分錯(cuò)誤和正常的結(jié)果
  // 我們用它來(lái)執(zhí)行
  next(result);
 }
 function next({ done, value }) {
  if (done) {
  return resolve(value);
  }
  // 假設(shè)我們總是接收 Promise,所以不需要檢查類(lèi)型
  return value.then(onFulfilled, onRejected);
 }
 });
}

關(guān)于 async/await

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

總結(jié)

以上所述是小編給大家介紹的在 JavaScript 中使用 Generator的方法,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評(píng)論