對(duì)于發(fā)布訂閱模式想必大家并不陌生,它在異步交互中具有很大的作用,能夠使我們的代碼結(jié)構(gòu)更加清晰易讀,便于維護(hù)。
node.js極速入門課程:進(jìn)入學(xué)習(xí)
在node
中我們可以使用 內(nèi)置模塊event
來(lái)實(shí)現(xiàn)發(fā)布訂閱模式,這篇文章我們將深入去學(xué)習(xí)event
并演示它在我們實(shí)際開(kāi)發(fā)中的作用,讓我們開(kāi)始吧!【相關(guān)教程推薦:nodejs視頻教程】
一、初步使用
引入event內(nèi)置模塊
// 引入內(nèi)置模塊event const EventEmitter = require("events");
創(chuàng)建event對(duì)象
event
內(nèi)置模塊本質(zhì)是一個(gè)構(gòu)造函數(shù),我們需要通過(guò)new
操作符去調(diào)用它
// 創(chuàng)建event對(duì)象 const event = new EventEmitter();
監(jiān)聽(tīng)事件
使用event
對(duì)象上的on
函數(shù)來(lái)定義一個(gè)監(jiān)聽(tīng)事件,語(yǔ)法為:event.on(事件名,事件處理函數(shù))
// 監(jiān)聽(tīng)run事件 event.on("run", (data) => { console.log("run事件運(yùn)行,參數(shù)為:", data); });
觸發(fā)事件
使用event
對(duì)象上的emit函數(shù)來(lái)觸發(fā)監(jiān)聽(tīng)的事件,語(yǔ)法為:event.emit(需要觸發(fā)的事件名,需要給事件處理函數(shù)傳遞的參數(shù))
// 觸發(fā)run事件 event.emit("run", "111111");
完整代碼
// 引入內(nèi)置模塊event const EventEmitter = require("events"); // 創(chuàng)建event對(duì)象 const event = new EventEmitter(); // 監(jiān)聽(tīng)run事件 event.on("run", (data) => { console.log("run運(yùn)行,參數(shù)為:", data); }); // 觸發(fā)run事件 event.emit("run", "111111");
運(yùn)行結(jié)果:
❗️ 事件重復(fù)監(jiān)聽(tīng)的問(wèn)題
==注意:當(dāng)同一事件被監(jiān)聽(tīng)多次時(shí),觸發(fā)事件時(shí)會(huì)同時(shí)觸發(fā)這個(gè)事件的所有事件處理函數(shù)==
二、應(yīng)用
在上一節(jié)Node.js | 搭建后端服務(wù)器(含內(nèi)置模塊 http | url | querystring 的使用)中有一個(gè)使用node
模擬get
請(qǐng)求(轉(zhuǎn)發(fā)跨域數(shù)據(jù))的案例:
const http = require("http"); const https = require("https"); // http和https的區(qū)別僅在于一個(gè)是http協(xié)議一個(gè)是https協(xié)議 const url = require("url"); const server = http.createServer(); server.on("request", (req, res) => { const urlObj = url.parse(req.url, true); res.writeHead(200, { "content-type": "application/json;charset=utf-8", "Access-Control-Allow-Origin": "http://127.0.0.1:5500", }); switch (urlObj.pathname) { case "/api/maoyan": // 我們定義的httpget方法:使node充當(dāng)客戶端去貓眼的接口獲取數(shù)據(jù) httpget((data) => res.end(data)); // 注意這里 break; default: res.end("404"); break; } }); server.listen(3000, () => { console.log("服務(wù)器啟動(dòng)啦!"); }); function httpget(cb) { // 定義一個(gè)存放數(shù)據(jù)的變量 let data = ""; // 因?yàn)樨堁鄣慕涌谑莌ttps協(xié)議的,所以我們需要引入https // http和https都具有一個(gè)get方法能夠發(fā)起get請(qǐng)求,區(qū)別是一個(gè)是http協(xié)議,一個(gè)是https協(xié)議 // http get方法第一個(gè)參數(shù)為接口地址,第二個(gè)參數(shù)為回調(diào)函數(shù) https.get( "https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4", (res) => { // http get方法獲取的數(shù)據(jù)是一點(diǎn)點(diǎn)返回的,并不是直接返回全部 // 監(jiān)聽(tīng)data,當(dāng)有數(shù)據(jù)返回時(shí)就會(huì)被調(diào)用 res.on("data", (chunk) => { // 收集數(shù)據(jù) data += chunk; }); // 監(jiān)聽(tīng)end,數(shù)據(jù)返回完畢后調(diào)用 res.on("end", () => { cb(data); // 注意這里 }); } ); }
注意上面代碼的第19行和第49行:
httpget((data) => res.end(data)); // 注意這里
cb(data); // 注意這里
這個(gè)例子中,我們是通過(guò)在httpget
函數(shù)中傳入一個(gè)回調(diào)函數(shù)來(lái)接收httpget
函數(shù)獲取到的數(shù)據(jù),這種寫法實(shí)際是沒(méi)有問(wèn)題的,在開(kāi)發(fā)中也常常進(jìn)行使用。
但在一些情況下,特別是函數(shù)多層嵌套調(diào)用時(shí)(如下面的例子),這種寫法就顯得不夠優(yōu)雅,因?yàn)樗拇a結(jié)構(gòu)不是很清晰,不能很直觀的看懂其邏輯:
function user() { getUser((data) => { console.log(data); }); } function getUser(cb) { // .... const id = 1; getUserInfo(cb, id); } function getUserInfo(cb, id) { // .... const name = id + "Ailjx"; cb(name); }
讓我們使用內(nèi)置模塊event
去改造一下上面node
模擬get
請(qǐng)求(轉(zhuǎn)發(fā)跨域數(shù)據(jù))的案例:
const http = require("http"); const https = require("https"); const url = require("url"); const EventEmitter = require("events"); const server = http.createServer(); // 存放event對(duì)象 let event = ""; server.on("request", (req, res) => { const urlObj = url.parse(req.url, true); res.writeHead(200, { "content-type": "application/json;charset=utf-8", "Access-Control-Allow-Origin": "http://127.0.0.1:5500", }); switch (urlObj.pathname) { case "/api/maoyan": event = new EventEmitter(); // 注意該位置 // 監(jiān)聽(tīng)事件 event.on("resEnd", (data) => { res.end(data); }); httpget(); break; default: res.end("404"); break; } }); server.listen(3000, () => { console.log("服務(wù)器啟動(dòng)啦!"); }); function httpget() { let data = ""; https.get( "https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4", (res) => { res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { // 觸發(fā)事件并傳遞數(shù)據(jù) event.emit("resEnd", data); }); } ); }
運(yùn)行并調(diào)用/api/maoyan
接口:
接口正常使用
注意上邊代碼new EventEmitter()
的位置,如果new EventEmitter()
是在外部的話,相當(dāng)于是只有一個(gè)全局的event
對(duì)象,當(dāng)我們每次調(diào)用/api/maoyan
接口時(shí),node
都會(huì)監(jiān)聽(tīng)一個(gè)新的resEnd
事件,這就會(huì)導(dǎo)致resEnd
事件被重復(fù)監(jiān)聽(tīng):
所以我們才需要將創(chuàng)建event
對(duì)象的代碼new EventEmitter()
寫到接口的case
分支里,這樣當(dāng)我們調(diào)用這個(gè)接口時(shí),會(huì)創(chuàng)建一個(gè)新的event
對(duì)象,老的event
對(duì)象被棄用會(huì)被JS
垃圾處理機(jī)制給處理掉,這樣就不會(huì)出現(xiàn)resEnd
事件被重復(fù)監(jiān)聽(tīng)的問(wèn)題