簡體   English   中英

在 Node.js 中編寫非阻塞函數的正確方法

[英]Correct way to write a non-blocking function in Node.js

我寫了一個返回 Promise 的簡單函數,所以應該是非阻塞的(在我看來)。 不幸的是,該程序似乎停止等待 Promise 完成。 我不確定這里有什么問題。

function longRunningFunc(val, mod) {
    return new Promise((resolve, reject) => {
        sum = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < val; j++) {
                sum += i + j % mod
            }
        }
        resolve(sum)
    })
}

console.log("before")
longRunningFunc(1000, 3).then((res) => {
    console.log("Result: " + res)
})
console.log("after")

輸出看起來像預期的那樣:

before     // delay before printing below lines
after
Result: 5000049900000

但是程序在打印第二行和第三行之前會等待。 你能解釋一下先打印“之前”和“之后”然后(一段時間后)結果的正確方法嗎?

將代碼包裝在 Promise 中(就像您所做的那樣)不會使其成為非阻塞的。 Promise 執行器函數(您傳遞給new Promise(fn)的回調被同步調用並將阻塞,這就是您看到獲取輸出延遲的原因。

實際上,沒有辦法創建自己的非阻塞純 Javascript 代碼(就像您擁有的那樣),除非將其放入子進程中,使用 WorkerThread,使用一些創建新的 Javascript 線程的第三方庫或使用用於線程的新實驗性 node.js API。 常規 node.js 將您的 Javascript 作為阻塞和單線程運行,無論它是否包含在 Promise 中。

您可以使用setTimeout()之類的東西來更改代碼運行的“時間”,但無論何時運行,它仍然會阻塞(一旦它開始執行,在它完成之前沒有其他任何東西可以運行)。 node.js 庫中的異步操作都使用某種形式的底層原生代碼,允許它們異步(或者它們只是使用其他本身使用原生代碼實現的 node.js 異步 API)。

但是程序在打印第二行和第三行之前會等待。 你能解釋一下先打印“之前”和“之后”然后(一段時間后)結果的正確方法嗎?

正如我上面所說,將事物包裝在 Promise executor 函數中不會使它們異步。 如果你想“改變”事情運行的時間(認為它們仍然是同步的),你可以使用setTimeout() ,但這並沒有真正使任何東西成為非阻塞,它只是讓它稍后運行(當它運行時仍然阻塞運行)。

所以,你可以這樣做:

function longRunningFunc(val, mod) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            sum = 0;
            for (var i = 0; i < 100000; i++) {
                for (var j = 0; j < val; j++) {
                    sum += i + j % mod
                }
            }
            resolve(sum)
        }, 10);
    })
}

這將重新安排耗時for循環稍后運行,並且可能“看起來”是非阻塞的,但它實際上仍然阻塞 - 它只是稍后運行。 要使其真正實現非阻塞,您必須使用前面提到的一種技術將其從 Javascript 主線程中取出。

在 node.js 中創建實際非阻塞代碼的方法:

  1. 在單獨的子進程中運行它,並在完成后獲得異步通知。
  2. 在 node.js v11 中使用新的實驗性工作線程
  3. 將您自己的本機代碼插件寫入 node.js,並在您的實現中使用 libuv 線程或操作系統級線程(或其他操作系統級異步工具)。
  4. 構建在先前存在的異步 API 之上,並且沒有您自己的代碼在主線程中花費很長時間。

Promise 的 executor 函數是同步運行的,這就是你的代碼阻塞執行主線程的原因。

為了不阻塞執行的主線程,您需要在執行長時間運行的任務時定期協作地讓出控制。 實際上,您需要將任務拆分為子任務,然后在事件循環的新滴答聲上協調子任務的運行。 通過這種方式,您可以為其他任務(如渲染和響應用戶輸入)提供運行機會。

您可以使用 promise API 編寫自己的異步循環,也可以使用異步函數。 異步函數支持函數的暫停和恢復(重入),並對您隱藏大部分復雜性。

以下代碼使用setTimeout將子任務移動到新的事件循環滴答聲上。 當然,這可以概括,批處理可以用來在任務進度和 UI 響應之間找到平衡; 此解決方案中的批量大小僅為 1,因此進展緩慢。

最后:這類問題的真正解決方案可能是Worker

 const $ = document.querySelector.bind(document) const BIG_NUMBER = 1000 let count = 0 // Note that this could also use requestIdleCallback or requestAnimationFrame const tick = (fn) => new Promise((resolve) => setTimeout(() => resolve(fn), 5)) async function longRunningTask(){ while (count++ < BIG_NUMBER) await tick() console.log(`A big number of loops done.`) } console.log(`*** STARTING ***`) longRunningTask().then(() => console.log(`*** COMPLETED ***`)) $('button').onclick = () => $('#output').innerHTML += `Current count is: ${count}<br/>`
 * { font-size: 16pt; color: gray; padding: 15px; }
 <button>Click me to see that the UI is still responsive.</button> <div id="output"></div>

暫無
暫無

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

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