浅谈NodeJS-JavaScript中的异步IO机制

JS中的异步I/O

异步原理

I/O 操作的模型有两种
第一种是 CPU 等待 I/O 操作执行完成获取到操作结果后再去执行其他指令,这是同步 I/O 操作 (阻塞
I/O)。
第二种是 CPU 不等待 I/O 操作执行完成,CPU 在发出 I/O 指令后,内存和磁盘开始工作,CPU 继续执
行其他指令。当 I/O 操作完成后再通知 CPU I/O 操作的结果是什么。这是异步 I/O 操作 (非阻塞 I/O) 。
同步 I/O 在代码中的表现就是代码暂停执行等待 I/O 操作,I/O 操作执行完成后再执行后续代码。
异步 I/O 在代码中的表现就是代码不暂停执行,I/O 操作后面的代码可以继续执行,当 I/O 操作执行完成
后通过
回调函数
的方式通知 CPU,说 I/O 操作已经完成了,基于 I/O 操作结果的其他操作可以执行了
(通知 CPU 调用回调函数)。
同步 I/O 和 异步 I/O 区别就是是否等待 I/O 结果。
Node 采用的就是异步非阻塞 I/O 模型

JS的运行时底层实现

在 Node.js 代码运行环境中,它为 JavaScript 代码的执行提供了一个主线程,通常我们所说的单线程指
的就是这个主线程,主线程用来执行所有的同步代码。但是 Node.js 代码运行环境本身是由 C++ 开发
的,在 Node.js 内部它依赖了一个叫做 libuv 的 c++ 库,在这个库中它维护了一个线程池,默认情况下
在这个线程池中存储了 4 个线程,JavaScript 中的异步代码就是在这些线程中执行的,所以说
JavaScript 代码的运行依靠了不止一个线程,所以 JavaScript 本质上还是多线程的。

回调函数

函数作为参数传递给另一个函数调用

function Thread1(log)
{
  console.log(log);
}

function MainThread(callBack)
{
  console.log("MainThread Start:");
  callBack("Thread1 Start:");
  console.log("MainThread End:");
}

MainThread(Thread1);

应用:通过回调函数执行异步API的结果

const fs = require('fs');
fs.readFile('./test.txt', "utf-8",(err, data) => 
{
  if(err) console.log(err + "error is happened.");
  else console.log(data);
})

回调函数可能引发的问题

回调函数如果出现嵌套将可能产生问题

  1. 代码可读性差:当回调函数嵌套层数较多时,代码会变得越来越难以阅读和理解。
  2. 错误处理困难:在多层嵌套的回调函数中,如果某个回调函数出现错误,很难追踪到具体的错误原因。
  3. 调试困难:由于回调函数是异步执行的,所以在调试过程中可能会遇到一些难以预料的问题,如变量值在不同时刻可能不同。
  4. 性能问题:过多的回调函数嵌套可能导致程序的性能下降,因为每个回调函数都需要等待上一个回调函数执行完毕后才能执行。

可以通过Promise函数 和 异步编程的方法解决。

Promise函数

可以将 Promise 理解为容器,用于包裹异步 API 的容器,当容器中的异步 API 执行完成后,
Promise 允许我们在容器的外面获取异步 API 的执行结果,从而避免回调函数嵌套。
Promise 中有三种状态, 分别为等待(pending),成功(fulfilled),失败(rejected)。
默认状态为等待,等待可以变为成功,等待可以变为失败。 状态一旦更改不可改变。

//promise方法
const promise = new Promise((resolve, reject) => {
    fs.readFile("file1.txt", "utf8", (err, data) => { //通过回调函数读取文件内容
        if (err) {
            reject(err);
        } else {
            resolve(data);
        }
    })
})

promise.then((data) => { //执行成功时打印(串行)
    console.log(data);
})
.catch(
    (err) => {
        console.log(err); //执行失败时打印(串行)
    }
);

Promise的链式调用

//promise的链式调用
function readFile(path) 
{
    return new Promise(
        (resolve, reject) =>
        {
            fs.readFile(path, "utf8", (err, data) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(data);
                }
            })
        }
    )
}

readFile("file1.txt").then
(
    (data) =>{
        console.log(data);
        return readFile("file2.txt").then(
            function(data){
                console.log(data).catch(
                    function(data){
                        console.log(data).finally(
                            function(){
                                console.log("finally")
                            }
                        )
                    }
                )
            }
        )
    }
)

Promise.all并发异步

Promise.finally([
    readFile("file1.txt"),
    readFile("file2.txt"),
    readFile("file3.txt")
]).then(
    (data) =>{
        console.log(data);
    }
)

异步函数

async 与 await

使用异步方法同样可以解决回调嵌套的问题
如下关键字:
async 声明异步函数的关键字,异步函数的返回值会被自动填充到 Promise 对象中。
await 关键字后面只能放置返回 Promise 对象的 API。
await 关键字可以暂停函数执行,等待 Promise 执行完后返回执行结果。
await 关键字只能出现在异步函数中。

async function async2()
{
    let x = await readFile("file1.txt");
    let y = await readFile("file2.txt");
    let z = await readFile("file3.txt");
    return [x,y,z];
}

async2().then(console.log)

util.promisify

在 Node.js 平台下,所有异步方法使用的都是基于回调函数的异步编程。为了使用异步函数提高异
步编程体验,可以使用 util 模块下面的 promisify 方法将基于回调函数的异步 API 转换成返回
Promise 的API。

const util = require("util");
const rf = util.promisify(fs.readFile);
async function async3()
{
    let x = await rf("file1.txt");
    let y = await rf("file2.txt");
    let z = await rf("file3.txt");
    return [x,y,z];
}

async3().then(console.log)

你可能感兴趣的:(NodeJS,javascript,开发语言,前端)