为什么异步操作在执行完后会自动执行回调

547 阅读2分钟

如果现在要对一个文件中的数据进行处理,首先要做的就是把数据读出来,于是就有一个读取数据的函数fetchData。对数据进行处理的过程一般会像下面这样:

function processData () {
    let data = fetchData();
    data += 1;
    return data;
}

但是如果fetchData需要花费很长的时间,那么这段程序就会阻塞,直到fetchData完成,才继续执行下面的语句,这显然影响了性能。于是这里就可以采用回调,让读取数据的同时可以去执行其他的代码,当读取完毕,再回来执行回调中的代码,避免阻塞。回调实际是一个函数,它会在把任务完成后被调用,比如这里是当数据读取完毕之后,再执行回调。过程如下:

function processData (callback) {
    fetchData(function (err, data) {
        if (err) {
            console.log("发生错误,终止");
            return callback(err);
        }
        data += 1;
        return callback(data);
    })
}

解释一下上面这段代码,里面有两个回调,fetchData里的参数匿名function是一个回调,它会在读取数据完毕后(不管读取成功还是读取失败)被调用,processData里的参数callback也是一个回调,实际它只是一个函数,在fetchData的回调代码中被调用,用来在fetchData之后对fetchData的结果进行处理(相当于processData就是拿着callback去处理fetchData读取到的数据的)。

一直有一个疑问,都知道异步函数执行完毕后会自动调用传进去的回调,那究竟是为什么会自动调用呢?比如setTimeout:

setTimeout(() => {
    console.log("Hello");
}, 5000);

5秒后会输出Hello,为什么会在5秒后自动输出Hello呢?原来异步函数是这样被定义的:

// 上面的fetchData就是一个asyncOperation
function asyncOperation (a, b, c, callback) {
    // 这里是大量读取数据的代码
    if (/*读取发生错误*/) {
        // 读取失败,调用callback
        return callback(new Error("出现错误"));
    }
    // 读取成功,调用callback
    callback(null, d, e, f);
}
asyncOperation(params..., function (error, returnVlaues) {
    // 这里的代码会在异步操作完成后执行
});

原来在异步函数定义的时候,就已经有调用callback的代码了。

调用callback的约定

callback一般被作为异步操作的最后一个参数,并且约定callback的第一个参数是error,用来获接收错误对象,第二、第三等参数可根据需要设置。

如果异步操作执行成功,那么在调用callback时就要把第一个参数设置为null,表示没有出错。
如果异步操作执行失败,那么就要在调用callback时将第一个参数设置为错误对象(Error object)

最后进入callback中执行相应的处理。这么一来就可以看到,异步操作完成后会自动调用回调。