Javascript承诺会阻止堆栈

Str*_*ch0 6 javascript javascript-events promise async-await

使用Javascript promises时,事件循环是否被阻止?

我的理解是使用await和async会使堆栈停止,直到操作完成.它是通过阻止堆栈来实现的,还是类似于回调并将进程传递给各种API?

Kai*_*ido 10

Javascript Promise 会阻塞堆栈吗

不,不是堆栈。当前作业将运行直至完成,然后 Promise 的回调开始执行。

使用 Javascript Promise 时,事件循环会被阻塞吗?

是的,它确实。

不同的环境有不同的事件循环处理模型,所以我将讨论浏览器中的事件循环处理模型,但尽管nodejs的模型更简单一些,但它们实际上暴露了相同的行为。

在浏览器中,Promises 的回调( ES 术语中的PromiseReactionJob)实际上是在所谓的微任务中执行的。
任务是一种特殊任务,在特殊的微任务队列中排队。在单个事件循环迭代期间,在所谓的微任务检查点中,以及每次JS 调用堆栈为空时,例如在主任务完成之后、在执行诸如调整大小之类的渲染事件之后,都会多次访问该微任务队列,在每个动画帧回调之后等等。

这些微任务检查点 是事件循环的一部分,并且会像任何其他任务一样在它们运行时阻止它。
然而,更重要的是,从微任务检查点调度的微任务将由同一个微任务检查点执行。

这意味着使用 Promise 的简单事实不会让您的代码让事件循环喘息,就像setTimeout()计划任务可以做的那样,即使 js 堆栈已被清空并且前一个任务在回调之前已完全执行被调用时,您仍然可以很好地完全锁定事件循环,永远不允许它处理任何其他任务,甚至更新渲染:

const log = document.getElementById( "log" );
let now = performance.now();
let i = 0;

const promLoop = () => {
  // only the final result will get painted
  // because the event-loop can never reach the "update the rendering steps"
  log.textContent = i++;
  if( performance.now() - now < 5000 ) {
    // this doesn't let the event-loop loop
    return Promise.resolve().then( promLoop );
  }
  else { i = 0; }
};

const taskLoop = () => {
  log.textContent = i++;
  if( performance.now() - now < 5000 ) {
    // this does let the event-loop loop
    postTask( taskLoop );
  }
  else { i = 0; }
};

document.getElementById( "prom-btn" ).onclick = start( promLoop );
document.getElementById( "task-btn" ).onclick = start( taskLoop );

function start( fn ) {
  return (evt) => {
    i = 0;
    now = performance.now();
    fn();
  };
}

// Posts a "macro-task".
// We could use setTimeout, but this method gets throttled
// to 4ms after 5 recursive calls.
// So instead we use either the incoming postTask API
// or the MesageChannel API which are not affected
// by this limitation
function postTask( task ) {
  // Available in Chrome 86+ under the 'Experimental Web Platforms' flag
  if( window.scheduler ) {
    return scheduler.postTask( task, { priority: "user-blocking" } );
  }
  else {
    const channel = postTask.channel ||= new MessageChannel();
    channel.port1
      .addEventListener( "message", () => task(), { once: true } );
    channel.port2.postMessage( "" );
    channel.port1.start();
  }
}
Run Code Online (Sandbox Code Playgroud)
<button id="prom-btn">use promises</button>
<button id="task-btn">use postTask</button>
<pre id="log"></pre>
Run Code Online (Sandbox Code Playgroud)

因此请注意,使用 Promise 对让事件循环实际循环没有任何帮助。

我们经常看到代码使用批处理模式来不阻止完全失败的 UI,因为它假设 Promises 会让事件循环循环。为此,请继续使用setTimeout()它来安排任务,或者在不久的将来使用postTaskAPI 。

我的理解是,使用等待和异步,使堆栈停止,直到操作完成。

有点......当awaiting 一个值时,它会将函数执行的剩余部分添加到附加到等待的 Promise 的回调中(这可以是解析非 Promise 值的新 Promise)。
所以此时堆栈确实被清除了,但是事件循环在这里根本没有被阻塞,相反它被释放来执行其他任何事情,直到 Promise 解析为止。

这意味着您可以很好地等待一个永不解决的承诺,并且仍然让您的浏览器正常运行。

async function fn() {

  console.log( "will wait a bit" );
  const prom = await new Promise( (res, rej) => {} );
  console.log( "done waiting" );
  
}
fn();

onmousemove = () => console.log( "still alive" );
Run Code Online (Sandbox Code Playgroud)
move your mouse to check if the page is locked
Run Code Online (Sandbox Code Playgroud)

  • 这是最有教养的答案 (2认同)

jfr*_*d00 7

使用Javascript promises时,事件循环是否被阻止?

不,Promise只是一个事件通知系统.它们本身不是一种操作.他们只是通过调用适当的.then().catch()处理程序来回应被解决或拒绝,如果链接到其他承诺,他们可以延迟调用这些处理程序,直到他们被链接的承诺也解决/拒绝.因为这样的承诺不会阻止任何事情,当然也不会阻止事件循环.

我的理解是使用await和async会使堆栈停止,直到操作完成.它是通过阻止堆栈来实现的,还是类似于回调并将进程传递给各种API?

await只是语法糖,用.then()一些简单的语法替换处理程序.但是,在幕后操作是相同的.在它之后的代码await基本上放在一个不可见的.then()处理程序中,并且没有阻塞事件循环,就像没有阻塞.then()处理程序一样.

  • 我相信这取决于承诺中执行的逻辑是否阻塞。无论任何包装承诺如何,CPU 密集型逻辑都会阻塞事件循环。 (2认同)

Ber*_*rgi 5

Anawait只阻塞 current async function,事件循环继续正常运行。当 promise 结束时,函数体的执行会从它停止的地方恢复。

每个async/await都可以在等效的.then(…)-callback 程序中转换,并且从并发的角度来看就像那样工作。因此,在awaited一个 promise 时,可能会触发其他事件并且可能会运行任意其他代码。

  • “等待只阻塞当前的异步函数”---我认为它需要更好的措辞,“阻塞”在这里令人困惑。 (4认同)