首頁 > web前端 > js教程 > 了解nodejs中的事件和事件循環

了解nodejs中的事件和事件循環

青灯夜游
發布: 2020-12-08 17:40:07
轉載
3307 人瀏覽過

了解nodejs中的事件和事件循環

相關推薦:《nodejs 教學

#熟悉javascript的朋友應該都使用過事件,例如滑鼠的移動,滑鼠的點擊,鍵盤的輸入等等。我們在javascript中監聽這些事件,從而觸發對應的處理。

同樣的nodejs中也有事件,而且還有一個專門的events模組來進行專門的處理。

同時事件和事件循環也是nodejs建構非同步IO的非常重要的概念。

今天我們來詳細了解一下。

事件

nodejs為事件提供了一個專門的模組:lib/events.js。

還記得我們在講使用nodejs建立web伺服器嗎?

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('welcome to www.flydean.com\n')
})
登入後複製

這裡,每個請求都會觸發request事件。

nodejs的核心API是基於非同步事件驅動程式來進行架構的,所以nodejs中有非常多的事件。

例如:net.Server 會在每次有新連線時觸發事件,fs.ReadStream 會在開啟檔案時觸發事件,stream會在資料可讀時觸發事件。

我們來看怎麼建構一個nodejs的事件:

const EventEmitter = require('events')
const eventEmitter = new EventEmitter()
登入後複製

events常用的方法有兩個,分別是on和emit。

on用來監聽事件,emit用來觸發事件。

eventEmitter.on('fire', () => {
  console.log('开火')
})

eventEmitter.emit('fire')
登入後複製

emit也可以帶參數,我們看下一個參數的情況:

eventEmitter.on('fire', who => {
  console.log(`开火 ${who}`)
})

eventEmitter.emit('fire', '美帝')
登入後複製

再看看兩個參數的情況:

eventEmitter.on('fire', (who, when) => {
  console.log(`开火 ${who} ${when}`)
})

eventEmitter.emit('fire', '川建国','now')
登入後複製

預設情況下,EventEmitter以註冊的順序同步地呼叫所有監聽器。這樣可以確保事件的正確排序,並有助於避免競態條件和邏輯錯誤。

如果需要非同步執行,則可以使用setImmediate() 或 process.nextTick()來切換到非同步執行模式。

eventEmitter.on('fire', (who, when) => {
    setImmediate(() => {
      console.log(`开火 ${who} ${when}`);
  });
})

eventEmitter.emit('fire', '川建国','now')
登入後複製

除此之外,events還支援其他幾個方法:

once(): 新增單次監聽器

removeListener() / off(): 從事件中移除事件監聽器

removeAllListeners(): 移除事件的所有監聽器

事件循環

我們知道nodejs的程式碼是運行在單執行緒環境中的,每次只會去處理一件事情。

這一種處理方式,避免了多執行緒環境的資料同步的問題,大大的提升了處理效率。

所謂事件循環,就是指處理器在一個程式週期中,處理完這個週期的事件之後,會進入下一個事件週期,處理下一個事件週期的事情,這樣一個週期一個週期的循環。

事件循環的阻塞

如果我們在事件處理過程中,某個事件的處理髮生了阻塞,則會影響其他的事件的執行,所以我們可以看到在JS中,幾乎所有的IO都是非阻塞的。這也是為什麼javascript有這麼多回呼的原因。

事件循環範例

我們看一個簡單的事件循環的例子:

const action2 = () => console.log('action2')

const action3 = () => console.log('action3')

const action1 = () => {
    console.log('action1')
    action2()
    action3()
}

action1()
登入後複製

上面的程式碼輸出:

action1
action2
action3
登入後複製

堆疊和訊息佇列

我們知道函數間的呼叫是透過堆疊來實現的,上面的例子中,我們的呼叫順序也是透過堆疊來實現的。

但不是函數中所有的方法都會入棧,還有一些方法會被放入訊息佇列。

我們再舉一個例子:

const action2 = () => console.log('action2')

const action3 = () => console.log('action3')

const action1 = () => {
    console.log('action1')
    setTimeout(action2, 0)
    action3()
}

action1()
登入後複製

上面的程式碼運行結果:

action1
action3
action2
登入後複製

結果不一樣了。這是因為settimeout觸發了定時器,當定時器到期的時候,回呼函數會被放入訊息佇列中等待被處理,而不是放入堆疊中。

事件循環會優先處理堆疊中的事件,只有堆疊中沒有任何資料的時候,才會去轉而消費訊息佇列中的事件。

雖然上面範例中setTimeout的timeout時間是0,但還是要等到action3執行完畢才能執行。

注意,setTimeout中的timeout並不是在目前的執行緒進行等待的,它是由瀏覽器或其他JS執行環境來呼叫。

作業佇列和promise

ES6中的Promise引入了作業佇列的概念,使用作業佇列將會盡快執行非同步函數的結果,而不是放在呼叫堆疊的末端。

舉例:

const action2 = () => console.log('action2')

const action3 = () => console.log('action3')

const action1 = () => {
    console.log('action1')
    setTimeout(action2, 0)
    new Promise((resolve, reject) =>
        resolve('应该在action3之后、action2之前')
    ).then(resolve => console.log(resolve))
    action3()
}

action1()
登入後複製

輸出結果:

action1
action3
应该在action3之后、action2之前
action2
登入後複製

這是因為,在目前函數結束之前 resolve 的 Promise 會在目前函數之後立即執行。

也就是說先執行堆疊,再執行作業佇列,最後執行訊息佇列。

process.nextTick()

先給大家一個定義叫做tick,一個tick就是指一個事件週期。而process.nextTick()就是指在下一個事件循環tick開始之前,呼叫這個函數:

process.nextTick(() => {
  console.log('i am the next tick');
})
登入後複製

所以nextTick一定要比訊息佇列的setTimeout快。

setImmediate()

nodejs提供了一個setImmediate方法,來盡快的執行程式碼。

setImmediate(() => {
  console.log('I am immediate!');
})
登入後複製

setImmediate中的函數會在事件循環的下一個迭代中執行。

setImmediate() 和 setTimeout(() => {}, 0)的功能基本上是類似的。它們都會在事件循環的下一個迭代中運行。

setInterval()

如果想要定時執行某些回呼函數,則需要使用到setInterval。

setInterval(() => {
  console.log('每隔2秒执行一次');
}, 2000)
登入後複製

要清除上面的定时任务,可以使用clearInterval:

const id = setInterval(() => {
  console.log('每隔2秒执行一次');
}, 2000)

clearInterval(id)
登入後複製

注意,setInterval是每隔n毫秒启动一个函数,不管该函数是否执行完毕。

如果一个函数执行时间太长,就会导致下一个函数同时执行的情况,怎么解决这个问题呢?

我们可以考虑在回调函数内部再次调用setTimeout,这样形成递归的setTimeout调用:

const myFunction = () => {
  console.log('做完后,隔2s再次执行!');

  setTimeout(myFunction, 2000)
}

setTimeout(myFunction, 2000)
登入後複製

更多编程相关知识,请访问:编程视频!!

以上是了解nodejs中的事件和事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:flydean
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板