node.js异步工作

93 阅读5分钟

node.js异步工作主要涉及的如何使得代码执行更加同步

node.js是基于事件驱动的,它是非阻塞的,所以在 网络请求/数据读写 等
方面都是异步的,而这些在node.js中又有对应的核心模块,比如http/file等

那么如何解决代码执行更加同步化?node.js提供了以下4中解决方案:

1.回调函数

2.es6 的promise,高级异步解决方案,万物皆可promise

3.基于promise的 async/await

4.事件驱动

当然,除上面四种解决方案之外,还涉及定时器方面(setTimeOut/setInterval)

回调函数

异步回调控制,当解决完成某个事件时的操作,容易导致回调地狱问题

fs.readFile('file1.txt', function(err, data) {
    // do something
    fs.readFile('file2.txt', function(err, data) {
        // do something
    });
});
/**
 * 本示例代码中使用了回调函数的概念
 *
 * 函数在js中是一等公民,可以作为参数传递,也可以作为其它参数的返回值
 *
 * 本示例中,callback函数就是一个回调函数并且是一个匿名函数,接受result以及status为返回值
 *
 * 回调函数在很多应用中得到应用,比如array数组的map方法,比如setTimeOut等等
 * */


function final(someInput, callback) {
    console.log('final')
    callback(`${someInput} and terminated by executing callback `,200);
}

function middleware(someInput, callback) {
    console.log('middleware')
    return final(`${someInput} touched by middleware `, callback);
}

function initiate() {
    const someInput = 'hello this is a function ';
    middleware(someInput, function (result,status) {
        console.log(result);
        console.log("status:"+status)
        // requires callback to `return` result
    });
}

initiate();

通过官方map函数理解回调函数:

/**
 *本示例为map中的callback函数调用
 *
 * 实现自己的myMap callback函数
 *
 * */

const numbers=[1,2,3,4,5]
const squares=numbers.map(function (num,index,array) {
    console.log(num+"=>"+index+"=>"+array)
    return num*num
})
console.log(squares)


/**
 * myMap写法和官方map类似
 * */
Array.prototype.myMap=function (callback) {//callback作为函数进行传递

    if(typeof callback !=='function'){
        throw new TypeError(`${callback} is not function`)
    }

    let newArray=[]

    //遍历
    for(let i=0;i<this.length;i++){
        newArray.push(callback(this[i],i,this))
    }

    //返回新数组
    return newArray
}

const myMapNumbers=numbers.myMap(function (num,index,array) {
    console.log(num+"=>"+index+"=>"+array)
    return num*num
})

console.log(myMapNumbers)

promise

promise是由es6提出,作用就是解决回调地狱,使得代码更加简洁可读,记住一点,万物 皆可promise

promise 分为三种状态,pendging,fulfilled,rejected,对于一个promise而言
如果没有进行resolve或者rejected,那么它是还没有结束的(一般配合async/await使用)

/**
 * 在es6中引入了 promise,promise是一种更高级的异步控制,能够解决回调地狱问题
 *
 * promise具有三个状态 pending(等待) fulfilled(完成) rejected(拒绝)
 *
 * 作为es6更高级的异步解决方案,万物皆可promise,只需要将其封装为promise即可
 *
 * promise的链式操作让代码层次化清晰
 *
 *
 * */

import fs from 'node:fs'

fs.readFile('./test.txt',function (err,data) {
    if(err){
        throw err
    }

    console.log(data.toString())
})

/**
 * 使用promise解决异步到同步编程
 * */
const  newPromise= new Promise(function (resolve, reject) {
    fs.readFile('./test.txt',function (err,data) {
        if(err){
            reject(err)
        }

        resolve(data)
    })
})

newPromise.then(res=>{
    console.log(0)
    console.log(res.toString())
}).then(res=>{
    console.log(1)
}).then(res=>{
    console.log(2)
}).catch(err=>{
    throw err
})


console.log('-----------------------------------------------')

/**
 *
 * 对于promise而言,只有rejected或者fulfilled状态时,才算状态完成,即resolve或者reject,
 *
 * 如果没有进行resolve或者reject,那么这个promise并没有进行结束,
 * 对于浏览器而言,因为界面一直打开,那么它会达到类似同步效果(需要使用await)
 *
 * 对于node.js环境而言,因为它是非阻塞的,如果没有i/o操作或者定时器,那么它是会直接结束的
 * */


let customResolve=null
async function test() {
    return await new Promise(function (resolve, reject) {
        console.log('hello')
        customResolve=resolve
    })
}


async function test1() {
    let data=await test()//阻塞
    console.log('data:'+data)
}

function test2() {
    console.log('teste2')
    customResolve('confirm')
}

test1()
test2()

async/await

async/await 是基于promise的,因为await之后就是获取对应promise接受的值,
而对于await作用于普通函数,实际上也是将函数返回的数据包装为一个promise
接受的值

/**
 *
 * node.js在异步控制中除了通过回调函数进行解决,还通过async/await
 *
 * async/await 是一种基于promise更高级的方式,使得代码看起来更加同步,所以本质上出现是以解决异步promise
 * 当await作用于promise时,返回得到的是已解决promise的值(不管是reject还是resolve)
 * 当作用于普通函数时,js将会将函数返回值包装为已解决的promise的值
 *
 * await表示等待执行其后函数,只有执行完成才执行下一步
 *
 *
 * */

import fs from 'node:fs'

const readFilePromise=function (path){
    return new Promise(function (resolve, reject) {
        fs.readFile(path,function (err,data) {
            if (err){
                reject(err)
            }

            resolve(data)
        })
    })
}

async function mode() {
    return 1234
}


async function readFile() {

    let data1=await readFilePromise('./test.txt')//作用于promise
    console.log(data1.toString())

    let data2=await mode()//作用于普通函数
    console.log(data2)
}

readFile()

事件驱动

node.js本身基于事件驱动,所以很多事情可以通过模拟进行

/**
 *
 * EventEmitter
 *
 * node.js 是基于事件驱动的单线程模型,这种模型是非阻塞的
 * 比如 文件读取/网络请求/数据查询 等,其中这些都有对应的模块,也是node.js的核心模块
 *
 * once 监听一次
 *
 * removeListener('event3',listener1) 删除某一个事件监听器,两个参数
 *
 * removeAllListeners 删除所有事件监听器
 *
 * */

import events from 'node:events'

const emitter = new events.EventEmitter();
emitter.on('event1', function() {
    // do something
    emitter.emit('event2','这是参数')
    console.log(11)
});

// emitter.once('event1', function() {//监听一次
//     // do something
//     emitter.emit('event2','这是参数')
//     console.log(11)
// });

emitter.on('event2',function (param1) {
    console.log(22+param1)
})

emitter.emit('event1');
// emitter.removeListener('event1')//删除
emitter.removeAllListeners()//删除所有,在这之后使用的emit都输出为空,不会报错
emitter.emit('event1');


console.log('----------------------------------')
function listener1() {
    console.log('listener1 triggered');
}

function listener2() {
    console.log('listener2 triggered');
}

emitter.on('event3', listener1);
emitter.on('event3', listener2);

emitter.emit('event3')
emitter.removeListener('event3',listener1)//两个参数
emitter.removeListener('event3',listener2)//两个参数

emitter.emit('event3')

定时器

setTimeOut以及setInterval, 当然还有setImmediate

/**
 *
 * setTimeout 延迟执行。只执行一次
 * 删除:clearTimeOut(id)
 *
 *
 *
 * setInterval 定时隔多久执行一次
 * 删除:clearInterval(id)
 *
  */


setTimeout(function () {
    console.log('hello,timeOut')
},2000)//2s

const timeOutId=setTimeout(function () {
    console.log('id')
},1000)//1s
// clearTimeout(timeOutId)  清除timeOutId

/**
 * 参数
 *
 * */
function timeOutFunc(param1,param2){
    console.log(param1)
    console.log(param2)
}
setTimeout(timeOutFunc,3000,'param1','param2')

setTimeout(function (param1, param2) {
    console.log(param1+param2)
},4000,1,2)

/**
 * 0延迟,尽快执行,但在当前函数执行之后
 * */
setTimeout(function () {
    console.log('after')
},0)
console.log('before-------before')

/**
 *
 * setInterval 定时执行
 *
 * */
let count=0;
const intervalId=setInterval(function () {
    if(count++===2){
        clearInterval(intervalId) //删除,但是删除之后依旧会执行一次后续代码
    }
    console.log('interval')
},2000)

/**
 *
 * 让一个函数同步执行,执行完这个函数之后间隔1s继续执行,然后间隔1s继续执行
 *
 * 为了防止函数执行交叉,使用如下方式
 *
 * 使用setInterval可能存在函数交叉,所以使用如下方式
 * */

const myFunction = () => {
    // do something
    console.log('myFunction')
    setTimeout(myFunction, 1000);
};

setTimeout(myFunction, 1000);