[英]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 中創建實際非阻塞代碼的方法:
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.