簡體   English   中英

為什么事件循環對微任務沒有超時?

[英]Why doesn't event loop have timeout for microtasks?

async function() {
  while (true) {
    await null;
  }
}

這將永遠阻塞線程,因為事件循環會嘗試在一幀內完成所有微任務。

超時就像:

  1. 事件循環在 1 秒后暫停處理下一個微任務。
  2. 引擎使用線程來處理其他代碼,例如動畫幀。
  3. 然后在下一幀中,事件循環繼續處理上一幀的微任務。

所以while (true)每幀只會阻塞線程 1 秒,因為await null; 是可暫停的。

Polyfill: yieldawait

const tasks = new Array;
const resolvedValues = new Array;
const runnables = new WeakSet;

function process(task, resolved) {
  try {
    const result = task.next(resolved);
    if (result.done) {
      return null;
    }
    const { value } = result;
    if (value === undefined) {
      tasks.unshift(task);
      resolvedValues.unshift(undefined);
      return;
    }
    Promise.resolve(value).then((value) => {
      tasks.unshift(task);
      resolvedValues.unshift(value);
      if (unsubscribed) {
        unsubscribed = false;
        requestAnimationFrame(listener)
      }
    })
  } catch (e) {
    console.error(e)
  }
  return null;
}

let unsubscribed = true;
const listener = function() {
  const ms = Date.now();
  while (Date.now() - ms < 100) {
    if (process(tasks.pop(), resolvedValues.pop()) === null) {
      break;
    }
  }
  if (tasks.length > 0) {
    requestAnimationFrame(listener);
    return;
  }
  unsubscribed = true
};

function suspend(runnable) {
  const task = runnable();
  if (runnables.has(task)) {
    return;
  }
  runnables.add(task);
  if (process(task) === null) {
    return;
  }
  if (unsubscribed) {
    unsubscribed = false;
    requestAnimationFrame(listener)
  }
}
export { suspend };
let n = 0;
suspend(function*() {
  while (true) {
    yield;
    n++;
    console.log("running")
  }
});

const resetCount = () => {
  console.log(n);
  n = 0;
  requestAnimationFrame(resetCount);
};
requestAnimationFrame(resetCount);

“這可能會破壞事物”,我同意,但究竟什么可能被破壞?

我並不是說我必須有這個功能。 我想知道為什么事件循環不是這樣設計的,在微任務/承諾開始存在之前,那時不需要向后兼容。

JavaScript 引擎是否有可能檢測到潛在的錯誤代碼?

是的!

這有用嗎?

當然可以。 畢竟,TypeScript 和(在某種程度上)ESLint 的工作是找到有問題的代碼並警告你。

引擎是否會針對潛在代碼發出警告?

有時! 例如,當被拒絕的 Promise 仍未被捕獲時,大多數引擎都會警告您。

他們為什么不為這個案子?

我不能代表 JavaScript 引擎制造商,但我可以想象他們是否會針對您的寵物問題實施警告/某些行為將基於許多因素,包括這是一個問題的可能性,檢測的難易程度以及引擎嘗試找出是否發生不良情況的成本是多少。 沒有什么是免費的,JavaScript 引擎需要快速。

在我看來,這對於某人來說是一個不太可能犯的錯誤,如果犯了這個錯誤,它很快就會被注意到,因為它的影響是多么糟糕。 這不像是需要手持的情況。

但即使這是一個常見問題,我覺得 ESLint 可能仍然是一個更好的地方來檢測和警告你。

[W] 究竟有什么可能被破壞?

假設所有微任務都在下一個任務之前處理的任何代碼。
顯然,使用微任務的原生 Web API 的設計方式不會進入這樣的循環,但用戶端代碼可能需要接近它的東西(例如在一個很長的數組中等待每個 Promise。
所以是的,他們可能會檢測到他們進入了一個無限循環,使用你的async函數可能比使用(function loop() { return Promise.resolve().then(loop); })()更容易並暫停就像他們確實檢測到while(true){}循環一樣。 但是對於同步版本,他們實際上並沒有分析代碼,他們確實測量了每個任務(或微任務)持續多長時間,並且由於在我們的微任務循環中每個微任務都不需要時間,所以它們不被關注。
因此,即使對於微任務循環,將來我們也會有相同的“腳本阻止您的選項卡”類型的消息,我不會感到驚訝,但這可能不是供應商目前不想花太多時間的事情,因為這真的只是對糟糕的代碼很好。

但是如果存在這樣的傻瓜守衛,它不應該恢復微任務,而只是停止並破壞它。

[W]hy [the] 事件循環不是這樣設計的

因為按照最初的設計,微任務是一種合並任務中發生的小事件的工具,因此所有這些事件都在完成整個任務之后,但在下一個任務開始之前得到處理。例如,其中一個導致今天微任務隊列設計的原始 API 是 MutationRecords API,我們希望在下一個繪畫幀之前處理所有 DOM 更改。 允許在中間“暫停”微任務檢查點會破壞我們在下一個任務開始之前處理所有微任務的承諾,並且仍然使用 MutationRecords 示例,我們可以看到 DOM 更改在下一個任務無效之前被渲染.

另外,請記住,每次事件循環迭代都會多次訪問微任務隊列。 基本上每次執行腳本時,都會觸發一個微任務檢查點。 因此,為了使您的設計能夠正常工作,我們需要幾個微任務隊列,也許事件循環處理模型的每一步都需要一個。 但是,我們可以讓一個requestAnimationFrame回調阻塞事件循環,那么下面的回調會發生什么?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM