node 学习 异步 http io

91 阅读5分钟

1.node 异步编程

概念

  1. js是在单线程处理程序
  2. I/O需要异步的回调
  3. 前端异步可以消除单线程的阻塞
  4. 放在Node后段执行可以优化cpu性能,同时执行多个异步任务

串连执行异步

//定义测输出方法
const showTime = (str) => {
	console.log(str)
}

1. 传统回调嵌套:地狱回调

exports.callback =() =>{
  setTimeout( () => {
      showTime("test1");
        setTimeout( () => {
            showTime("test2");
        })
  })
}

2. 使用promise

const promise = (name,delay = 100) => {
	return new Promise(resolve => {
    	setTimeout( () => {
        	showTime(name);
            resolve();
        })
    }) 
}

//每一次then 都要返回new Promise()对象
exports.promiseTest = () => {
    promise('Promise1',600)
        .then((res) => {
            return promise('Promise2',500)
        })
        .then(
            (res) => {
                return promise('Promise3',100)
            })
}

//错误写法,该写法无法按需执行 必须正常返回new Promise对象才可以串行执行 
exports.promiseTest = () => {
    promise('Promise1',600)
        .then(promise('Promise2',100) )
        .then(promise('Promise2',200) )
}

3.使用gennerator 实现异步

let generatorTest = () => {
    const generator = function* (name) {
        yield promise(name + 1,500)
        yield promise(name + 2,100)
        yield promise(name + 3,200)
        yield promise(name + 4,400)
    } 

    function myGen(gen) { 
        let promise1 = gen.next().value;
        if(promise1){
            promise1.then(
                res => { 
                    myGen(gen)
                }
            )
        }
    } 
    myGen(generator('hello'))  
}

generatorTest();

4.使用event订阅发布 实现异步


class MyEvent {
    fnMap = {}//通过对象的key value 方式存储key 与 fnList 回调的数组方法 关系
    constructor(){

    }
    addEvent(key,fn){ //订阅事件
        if(!(key in this.fnMap)){
            this.fnMap[key] = []
        }
        this.fnMap[key].push(fn)
    }
    dispatchEvent(key,...arg){ //发布事件
        if(!(key in this.fnMap)){
           console.error(key,"事件未注册")
           return;
        }
        this.fnMap[key].forEach(fn => {
            fn(...arg);
        }); 
    }
}

let myEvent = new MyEvent();
let i = 0 //每次递增

let callFn = (name) => () => {
    console.log("调用成功",name)
    myEvent.dispatchEvent("end")
}

let fnList = [
    callFn("test1"),
    callFn("test2"),
    callFn("test3"),
]

myEvent.addEvent("end", () => {
    
    if(i < fnList.length ){
        let callIndex = i //这里需要特殊处理 提前把 i++ 再执行fnList 里面的方法
        i++
        fnList[callIndex]() //也可以写成  fnList[i++]() 一步到位
        //如果 i++  写在这里 
        // fnList 会一直循环调用 callFn() -> dispatchEvent() -> addEvent() 死循环,以至于i++ 不能正常递增
    }
})

myEvent.dispatchEvent("end")  


// 输出结果:
// 调用成功 test1
// 调用成功 test2
// 调用成功 test3

2.node 串行实现

promisify

const fs = require('fs');
// 同步调用
const data = fs.readFileSync('./conf.js'); //代码会阻塞在这里
console.log(data);
// 异步调用
fs.readFile('./conf.js', (err, data) => {
if (err) throw err;
console.log(data);
})
// promisify
const {promisify} = require('util')
const readFile = promisify(fs.readFile)
//1.使用then
readFile('./conf.js').then(data=>console.log(data.toString()))
//2.使用async await
(async ()=> {
    const readFile = promisify(fs.readFile)
    let data = await readFile('./conf.js');
    console.log(data.toString())
    }
)();


3.node + io

buffer

为数据缓冲对象,是一个类似数组结构的对象,可以通过指定开始写入的位置及写入的数据长度,往其中写入二进制数据

//创建长度为10个字节的 buf 
const buf1 = Buffer.alloc(10)
console.log(buf1)

//创建字符集为ascii 为 a 的 
const buf11 = Buffer.from("a")
console.log(buf11)

//创建字符集为 utf-8 的 xxx测试
const buf2 = Buffer.from("xxx测试")
console.log(buf2)

//拼接两个buff对象
const buf3 = Buffer.concat([buf1,buf2]) 
console.log(buf3)


//拼接两个buff对象
buf1.write("ggg")
console.log(buf1.toString())

//打印对应的字符集内容
console.log(buf1)

stream

是对buffer对象的高级封装,其操作的底层还是buffer对象,stream可以设置为可读、可写,或者即可读也可写,在nodejs中继承了EventEmitter接口,可以监听读入、写入的过程。具体实现有文件流,httpresponse等

//通过流复制图片
const fs = require('fs')
//创建流
const rs = fs.createReadStream("./img.png")//被复制的图片
const ws = fs.createWriteStream("./img42.png")//创建后的图片
rs.pipe(ws)

//服务器 图片处理:
//响应图片请求 
const {url, method, headers} = request;
else if (method === 'GET' && headers.accept.indexOf('image/*') !== -1) { fs.createReadStream('.'+url).pipe(response);//直接把流输出给返回的response
}

4. node 路径Path库 相对与绝对

如在 /Users/jason_pro/Desktop/test.js , 执行 node test.js

	__dirname  //获取test.js文件夹路径,输出: Users/jason_pro/Desktop/
	__filename  //获取test.js的完整绝对文件路径 输出: Users/jason_pro/Desktop/test.js

path

var path = require("path")

path.resolve() === __dirname 意思一样 Users/jason_pro/Desktop/
path.resolve(__dirname) === __dirname   意思一样 Users/jason_pro/Desktop/
path.resolve(__dirname,'dist/') === __dirname  + 'dist/'  Users/jason_pro/Desktop/dist

 
* 多个地址则从左边第一个开始,依次被右边的路径合并,如:
 	path.resolve('/dist','aaa/','bbb/')   ,输出 /dist/aaa/bbb
* 遇到/开头,则左边的原来的路径会被覆盖,如:
 	path.resolve('/dist','/aaa/','bbb/')   ,输出 /aaa/bbb
* 遇到../开头,向上跳一级
 	path.resolve('/dist/aaa/bbb','../ccc/','bbb/')   ,输出 /dist/aaa/ccc/bbb/

注意:

path.resolve('/dist') 如果加入 '/'开头 则 原来的绝对路径失效,输出 /dist
path.resolve('/dist','aaa/') //合并两个地址 ,输出 /dist/aaa
* 结尾的/ 可以省略
如:path.resolve('/dist','aaa/') = path.resolve('/dist','aaa')

5.node + http

Content-Type的类型

  • html网页 : text/html
  • text文本 : text/plain
  • json格式 : application/json;
  • 图片png :image/png
  • pdf格式 :application/pdf
const http = require('http')
const fs = require("fs"); 
const server = http.createServer((request, reponse) => {
    const { url, method, headers } = request;
    if (url === "/") {//首页
        reponse.statusCode = 200
        reponse.setHeader("Content-Type","text/html")
        fs.readFile("./index.html",(err,buf) => {
            reponse.end(buf)
        })
    } else if (url === "/userInfo") {//调用接口
        reponse.writeHead("200", { "Content-Type": "application/json" })
        const json = {"aa":222,b:3333}
        reponse.end(JSON.stringify(json))
    } else if (headers.accept.indexOf("image/") !== -1) {//图片流处理
        fs.createReadStream("./"+url).pipe(reponse)
    }
})
server.listen(3000, ()=>{
    console.log("服务启动成功")
})

6.node + http + socket.io

WebSocket 与 Socket.IO的关系

WebSocket是H5的规范,是一个标准的h5的socket协议

Socket.IO 是多种轮询机制以及其他实时通信方式,并封装了通用的接口。 这些方式包含 Adobe Flash Socket、Ajax 长轮询、Ajax multipart streaming 、持久 Iframe、JSONP 轮询等 WebSocket只是其中的一种。 当 Socket.IO 检测到当前环境不支持 WebSocket 时,能够自动地选择最佳的方式来实现网络的实时通信。

例子

运行 node index.js

index.js

const express = require('express')()
const http = require('http').Server(express)
const socketIO = require("socket.io")(http)//由于参数只能是http 需要转换为传统的 http库

socketIO.on('connection', (socket) => { //每个打开页面的用户连接后的 闭包处理
    // socket 为当前打开页面的客户端 
    //添加监听到消息方法
    socket.on("chat message", (msg)=> {
        //发送广播给所有人 
        socketIO.emit("chat message",msg);
    })
    socket.on("disconnection", () => {
        console.log("链接已断开")
    })
})
// 注意使用的是 http启动 应为stocketIO绑定在http上
http.listen(3000,()=> {
    console.log("启动3000 成功")
})

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title> 
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
    <script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
    <script>
      $(function() {
        var socket = io("http://127.0.0.1:3000");//由于是本地自己服务,所以可以简写 io(),
        //当然也支持跨域,可以是 其他服务器上的地址如:api.baidu.com:3000
        $("form").submit(function(e) {
          e.preventDefault(); // 避免表单提交行为
          socket.emit("chat message", $("#m").val());
          $("#m").val("");
          return false;
        });

        socket.on("chat message", function(msg) {
          $("#messages").append($("<li>").text(msg));
        });
      });
    </script>
  </body>
</html>

node 实现数据爬虫

const originRequest = require("request");
const cheerio = require("cheerio");
const iconv = require("iconv-lite");

function request(url, callback) {
    const options = {
        // url: url,
        encoding: null
    };
    originRequest(url, options, callback);
}

for (let i = 103234; i < 103238; i++) {
    const url = `https://www.dy2018.com/i/${i}.html`;
    request(url, function (err, res, body) {
        console.log("body",body,err) //这里err为 unable to verify the first certificate 有可能出现 待处理
        // const html = iconv.decode(body, "gb2312");
        // const $ = cheerio.load(html);
        // console.log($(".title_all h1").text());
    });
}

//unable to verify the first certificate 有可能出现 待处理