readfile异步读取_JavaScript从零开始——异步操作(5)

50fa6abbe7ae6bacb2869a90c7cca33d.png

好了,上一章节看过了Generator的基础使用,接下来就正式看看它如何进行异步操作吧,这部分并不算太复杂。

不过还是那句话,异步操作是JavaScript的非常重要的核心,如果没有这个功能,JS到今天仍然还是执行效率非常差劲的(卡得要死)上不了台面的脚本语言;ES 6原生支持PromiseGenerator给它带来的进步绝不是一星半点。


1 复习

ES 6 诞生以前,异步编程的方法,大概有下面四种。

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise 对象(特指由其他第三方库实现的方法,不是ES 6的原生Promise

Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。

2 基本概念

2.1 异步

所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。

2.2 回调函数

JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。回调函数的英语名字callback,直译过来就是"重新调用"。

读取文件进行处理,是这样写的(这是Node.jsFile System模块,简写为fs):

fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
    
  if (err) throw err;
  console.log(data);
});

上面代码中,readFile函数的第三个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行。

一个有趣的问题是,为什么 Node 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?

原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。

2.3 Promise

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下:

fs.readFile(fileA, 'utf-8', function (err, data) {
    
  fs.readFile(fileB, 'utf-8', function (err, data) {
    
    // ...
  });
});

不难想象,如果依次读取两个以上的文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。因为多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为"回调函数地狱"(callback hell)。

Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用 Promise,连续读取多个文件,写法如下:

let readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
    
  console.log(data.toString());
})
.then(function () {
    
  return readFile(fileB);
})
.then(function (data) {
    
  console.log(data.toString());
})
.catch(function (err) {
    
  console.log(err);
});

上面代码中,我使用了fs-readfile-promise模块,它的作用就是返回一个 Promise 版本的readFile函数。Promise 提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。

可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

那么,有没有更好的写法呢?

3 Generator 函数(回顾前一章节)

3.1 协程

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。它的运行流程大致如下。

  • 第一步,协程A开始执行。
  • 第二步,协程A执行到一半,进入暂停,执行权转移到协程B
  • 第三步,(一段时间后)协程B交还执行权。
  • 第四步,协程A恢复执行。

上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下:

function* asyncJob() {
    
  // ...其他代码
  let f = yield readFile(fileA);
  // ...其他代码
}

上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。

协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作࿰

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`ReadFile` 函数也是 Windows API 中的一个函数,用于从文件或设备读取数据。和 `WriteFile` 函数一样,`ReadFile` 函数也可以实现异步 I/O 操作。 在异步 I/O 操作中,`ReadFile` 函数不会阻塞线程,而是立即返回。当数据读取完成时,操作系统会通知应用程序。为了实现异步 I/O 操作,需要使用 `ReadFile` 函数的重载版本,该函数包含一个指向 `OVERLAPPED` 结构体的指针参数。 `OVERLAPPED` 结构体定义如下: ```c++ typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; }; PVOID Pointer; }; HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; ``` `ReadFile` 函数的异步操作流程如下: 1. 创建一个 `OVERLAPPED` 结构体,并设置好相关参数。 2. 调用 `ReadFile` 函数,并传入 `OVERLAPPED` 结构体指针作为最后一个参数。 3. 如果 `ReadFile` 函数返回值为 `FALSE`,则可以通过 `GetLastError` 函数获取错误码。 4. 如果 `ReadFile` 函数返回值为 `TRUE`,则需要等待异步操作完成。可以使用 `GetOverlappedResult` 或者 `WaitForSingleObject` 函数等待异步操作完成,并获取返回值。 需要注意的是,异步 I/O 操作需要使用多线程或者事件通知机制来处理异步操作完成的通知。例如,可以使用 `WaitForSingleObject` 函数等待异步操作完成,并在异步操作完成时设置事件对象,从而通知其他线程操作已完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值