04. nodejs的異步、回調、非阻塞IO、事件驅動

04 nodejs的異步、回調、非阻塞IO、事件驅動

​ nodejs具有單線程、非阻塞io、事件驅動的特性,非阻塞io和單線程聯繫在一起,異步非阻塞的特性適合高併發場景,並且優於java和php等傳統後臺語言。具體請看前一篇文章的理解和分析:

異步程序的寫法

回調函數

nodejs的異步提供了強大的併發處理能力,但是在寫程序時需要注意,他的語法並不是像java和php一樣是從上到下執行的,具體看下邊的例子

//延時獲取數據
const asynGetData = function () {//錯誤的示範
  let result = null;
  setTimeout(() => {
    result = "apple";
  }, 1000);
  return result;
}
console.log(asynGetData());//打印數據

​ [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hVQ02ptg-1575854807018)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208184358138.png)]

按照同步的寫法會發現程序的執行結果直接輸出了null而不是延時一秒打印出apple,這是因爲setTimeout是一個異步函數,通俗的解釋是主線程執行到setTimout函數時並不會真正的阻塞1秒鐘,而是直接把這個延時交給其他線程來做,主線程繼續向下執行,直到滿足延時1秒條件後,主線程繼續執行等待一秒完成後的回調函數:result = "apple";,因此輸出結果是null。

​ 那麼怎麼才能實現延時一秒獲取數據這個功能呢,這裏就得利用到回調函數了。(這裏指的是ES5之前的語法,ES6以後可以通過generator函數和promise以及await等語法糖實現同步寫異步功能)。

​ 通過callback實現如下;

let aysnGetData = function (callback) {
  let result = null;
  setTimeout(() => {
    result = "apple";
    callback(result);
  }, 1000);
}

function output(res) {//callback函數
  console.log(res);
}

aysnGetData(output);

或者直接傳入回調函數

//通過過回調函數實現同步編寫異步功能方式二:直接傳入回調函數
let aysnGetData = function (callback) {
  let result = null;
  setTimeout(() => {
    result = "apple";
    callback(result);
  }, 1000);
}
aysnGetData(function (res) {
  console.log(res);
});

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Rt9MohTE-1575854807021)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191208184339111.png)]

之前的文章有個遺留的問題:

  1. 如何在異步函數執行完畢後進行下一步處理?也就是如何判斷異步函數執行完畢?

答案是通過callback。

events 模塊

除了上述的callback實現,nodejs還提供了events模塊來處理異步。event模塊提供EventEmitter方法,返回的實例具有監聽和發射功能,類似於設計模式中的監聽者模式。通過events模塊實現上邊延時獲取數據的功能。

let events = require("events");
let EventEmitter = new events.EventEmitter();
//監聽函數
EventEmitter.on("getDataOk", function (val) {
  console.log("監聽到了 getDataOk 事件")
  console.log("獲取數據成功,result的值是:", val);
})
setTimeout(() => {
  console.log("開始觸發 getDataOk事件")
  EventEmitter.emit("getDataOk", "apple")
}, 1000);
  • new events.EventEmitter():生成 emitter實例對象
  • on(事件標誌,callback):用於監聽事件,當監聽到指定標誌的時間後執行回調函數
  • emit(事件標誌,要傳遞的值):發出事件並傳遞值。

有了events模塊就可以方便的進行異步處理了,在之前編寫簡單靜態web服務器中,用於通過擴展名獲取對應ContentType時,用到了同步讀文件函數readFileAsyn,有了events模塊,我們也可以使用異步讀文件函數實現簡單web服務器了。

重構靜態web服務器

const fs = require("fs");
const url = require("url");
const http = require("http");
const path = require("path");
const events = require("events");
const mimeModal = require("./module/mimeModalByEvent.js")
const app = http.createServer(function (req, res) {
  if (req.url == "/") {
    req.url = "/index.html";
  }
  if (req.url == "/favicon.ico") {
    return;
  }
  let pathname = url.parse(req.url).pathname;//獲取要請求的文件
  console.log("請求的pathname是:", pathname);
  let exname = path.extname(pathname);//獲取文件擴展名
  console.log("文件擴展名是:", exname);

  let fileLocation = "../../04.static_web/static/" + pathname;
  let notFound = "../../04.static_web/static/404.html";
  fs.readFile(fileLocation, (err, data) => {
    if (err) {
      console.log(404);
      console.log(`文件${pathname}沒有在 ${fileLocation} 處找到!`)
      res.writeHead(404, { "Content-Type": "text/html;charset='utf-8'" });
      res.write(notFound);//發送404頁面
      res.end();
    } else {
      // 獲取文件擴展名對應的ContentType
      let eventEmitter = new events.EventEmitter();
      mimeModal.getMime(eventEmitter, exname);

      eventEmitter.on("readContentTypeOk", (val) => {
        console.log("監聽到了readContentTypeOk事件,開始像瀏覽器返回數據");
        res.writeHead(200, { "Content-Type": val + ";charset='utf-8'" });
        res.write(data);
        res.end();
      })
    }
  })


});
app.listen(9999, "127.0.0.1")
console.log("server listen on 127.0.0.1:9999")

mimeModalByEvent.js文件代碼:

const fs = require("fs");
exports.getMime = function (eventEmitter, exname) {
  fs.readFile("./mime.json", (err, data) => {
    if (err) {
      console.log("mime.json讀取失敗")
    } else {
      let contentType = JSON.parse(data.toString())[exname];
      eventEmitter.emit("readContentTypeOk", contentType);
    }
  })
}

與之前的區別是用到了EventEmitter來通知函數文件讀取完畢

C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191209092225502.png通過截圖可以看出,當nodejs接受到瀏覽器請求後,讀取文件交給其他線程來完成,主線程不阻塞讀取文件這裏,而是繼續接收請求,等文件讀取完畢後通過events庫來通知主線程返回對瀏覽器的響應。這就是nodejs異步非阻塞特點的具體體現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章