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异步非阻塞特点的具体体现。

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