Node.js异步处理的各种写法

KennedyHilda 发布于1月前
0 条问题

异步的“坑”

最近一段时间参与开发了一个Node.js后台项目,作为一个PHP开发者,上手项目本身并不难,但是开发的过程却并不顺利,不顺利的主要原因在于思路上没有转变,没有从同步的思维转换到异步的思维。

所谓同步,就是程序(进程/线程)在一个任务的处理过程中,不会插入处理其他任务,即使遇到IO等不占CPU的操作,也会一直等待其结束才会继续往下处理。

所谓异步,就是程序(进程/线程)在一个任务的处理过程中,会插入处理其他任务,如遇到IO操作,当前任务会将程序(进程/线程)的控制权释放给其他任务,等IO操作结果返回后再继续往下处理。

简单地讲,同步不会释放控制权,异步会释放控制权。

众所周知,Node.js采用的是单线程的异步模型,在具体代码的写法上自然和PHP等同步模型不一样。在具体项目开发的过程中,各种异步操作相关的关键字层出不穷,如:.then()function* ... yieldasync...await等等。为了写一个类似同步的操作,比如:“在执行完A步骤拿到结果之后再执行B步骤”这么一个简单的需求,却要经过大量的反复调试验证才能解决。究其原因,就是对于这些异步操作的场景和关键字的含义理解不到位,异步操作所提供的选择太多了。

下面就结合代码实例,理一理这些异步操作方案究竟怎么使用。

异步的各种写法

任务说明:项目根目录下有三个文件Jay.txtAngela.txtHenry.txt,依次读取这三个文件的内容并打印。

下面使用各种异步处理的方法来完成此任务。

回调函数

const fs = require('fs');

fs.readFile('Jay.txt', 'utf8', function (err, data) {
    if (err) throw err;
    console.log(data);
    fs.readFile('Angela.txt', 'utf8', function (err, data) {
        if (err) throw err;
        console.log(data);
        fs.readFile('Henry.txt', 'utf8', function (err, data) {
            if (err) throw err;
            console.log(data);
        });
    });
});

console.log("finish");
1、函数 fs.readFile() 用于异步读取文件的所有内容,该函数本身没有返回值。读取的文件内容异步返回后通过回调函数处理。
2、函数 fs.readFile()的第二个参数是可选参数,如果指定了编码方式,则返回对应编码方式的字符串;如果没有指定,则返回文件的二进制内容,对应类型为Buffer,可以通过 buf.toString() 方法转换成对应的字符串。
3、回调函数的第一个参数必须是 错误对象,如果没有错误则错误对象的值为 null

执行程序:

$ node 0A_callback_01.js
finish
Hello, I'm Jay.
Hello, I'm Angela.
Hello, I'm Henry.

程序最先返回finish,是因为函数fs.readFile()是异步处理的,在调用后会直接继续往下处理,在文件内容返回后通过注册的回调函数处理。

串行和并行

通常,人们总是分不清同步异步串行并行之前的区别,朴素地认为:同步就是串行,异步就是并行。这么讲似乎对又似乎不对。

同步异步是从程序(进程/线程)执行方式的角度来看的,文章开头已经简单讲过同步异步的概念和区别。如果在程序的执行的过程中不发生任务切换,即:做当前任务的一件事情,等待这件事情完成后,再做当前任务的下一件事情,直到当前任务完成,这种方式就是同步。如果在程序的执行的过程中发生任务切换,即:做当前任务的一件事情,不等待这件事情做完,直接转去做其他任务,再做当前任务的下一件事情,如此往复,直到当前任务完成,这种方式就是异步

串行并行是从任务(事情)的角度来看的,如果多个任务(事情)不能同时做,而是做完一个才能做下一个,则就将这几个任务(事情)称作是串行的。如果多个任务(事情)可以同时做,则就将这几个任务(事情)称作是并行的。

同步就是串行这句话在一定程度上是正确的,因为同步程序做完一件事情,才会做下一件事情,从两件事情上看,是不会同时做的,所以同步程序只能串行地做事情。

异步就是并行这句话就不是那么回事了,异步程序可以选择串行地做事情,也可以选择并行地做事情,是串行做还是并行做取决于具体的业务场景。对于一个任务下的两件事情A和B,如果B依赖于A的结果,则需要串行;如果B不依赖于A的结果,则可以并行。还是以本文的读取三个文件为例,上面的代码示例就是串行执行的,依次读取"Jay.txt""Angela.txt""Henry.txt"的内容并打印出来。如果要改成并行执行该怎么做呢?简单做个改造就行,如下。

const fs = require('fs');

fs.readFile('Jay.txt', 'utf8', function (err, data) {
    if (err) throw err;
    console.log(data);
});

fs.readFile('Angela.txt', 'utf8', function (err, data) {
        if (err) throw err;
        console.log(data);
});

fs.readFile('Henry.txt', 'utf8', function (err, data) {
    if (err) throw err;
    console.log(data);
});

console.log("finish");

执行程序:

finish
Hello, I'm Angela.
Hello, I'm Jay.
Hello, I'm Henry.
从结果也可以看出,由于三个文件是并行读取的,所以哪个先读完是 随机的,和代码写的顺序无关。 按顺序写的代码就会按顺序执行,这是典型的 同步编程思维,要尽快转变过来,否则早晚有一天会“翻车”的。

Promise对象

Promise对象能够表示一个异步操作的状态和结果,使用其提供的.then()方法可以将多个多个异步操作“串联”起来,.then()方法本身也返回一个Promise对象。

同样是按顺序读取三个文件的任务,示例如下:

var readFilePromise = require('fs-readfile-promise');

readFilePromise('Jay.txt', 'utf8')
    .then(function(data) {
        console.log(data);
    })
    .then(function() {
        return readFilePromise('Angela.txt', 'utf8');
    })
    .then(function(data) {
        console.log(data);
    })
    .then(function() {
        return readFilePromise('Henry.txt', 'utf8');
    })
    .then(function(data) {
        console.log(data);
    })
    .catch(function(err) {
        console.log(err);
    });

console.log("finish");

执行程序:

finish
Hello, I'm Jay.
Hello, I'm Angela.
Hello, I'm Henry.

Promise对象有pending(初始)fulfilled(成功)rejected(失败)三种状态。当异步操作成功时,Promise对象从pending状态变为fulfilled状态,并将成功结果传递给.then()方法的第一个参数(也叫onfulfilled函数);当异步操作失败时,Promise对象从pending状态变为rejected状态,并将失败信息传递给.then()方法的第二个参数(也叫onrejected函数),如果没有指定第二个参数,则将失败信息传递给.catch()方法的参数(同样也叫onrejected函数)。

上面的程序,可以将上个文件的处理和下个文件的读取合并到一个.then()当中,示例如下:

var readFilePromise = require('fs-readfile-promise');

readFilePromise('Jay.txt', 'utf8')
    .then(function(data) {
        console.log(data);
        return readFilePromise('Angela.txt', 'utf8');
    })
    .then(function(data) {
        console.log(data);
        return readFilePromise('Henry.txt', 'utf8');
    })
    .then(function(data) {
        console.log(data);
    })
    .catch(function(err) {
        console.log(err);
    });

console.log("finish");
.then()当中,可以返回一个Promise对象,可以返回一个基础类型的值(数字、字符串、布尔值),也可以什么都不返回(直接 return;),甚至连 return语句都可以省略。这几种场景下的处理方式参考: .then()方法的 返回值说明

查看原文: Node.js异步处理的各种写法

  • lazybird
  • crazykoala
  • organicmeercat
  • organicbear
  • whitedog
  • smallbutterfly
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。