异步编程方案进化论

450 阅读2分钟

异步方案进化过程: callback ---> promise ---> generator + co ---> async + await 本文以nodejs中最常用的文件异步读取操作readFile为例,来说明各个方案下的实现方式。

前提

假设我们现在有两个文件1.txt和2.txt,1.txt的内容是'./2.txt'(2的路径),2.txt的内容是'异步,又见异步'。

需求1

拿到1的内容作为路径去读取2的内容打印出来。

需求2

同时读取两个文件,在两个文件都读取完成时,将文件中的内容合并。

callback

需求1实现

fs.readFileSync('./1.txt', 'utf8', function(err, data) {
    if(err) { throw err; }

    fs.readFileSync(data, 'utf8', function(err, data) {
        if(err) { throw err; }

        console.log(data);
    })
})

此时我们很容易利用了回调嵌套,嵌套多了就容易累积出回调地狱。

需求2实现

这个需求实现我们先需要了解一个lodash的_after函数

_after函数

_after函数返回一个函数,在这个函数调用次数达到一定限度时,调用回调函数。下面写一个自己的after函数:

function after(times, callback) {
    return function() {
        if(--times === 0) {
            callback();
        }
    };
}

let sleep = after(3, function() {
    console.log('睡饱了');
});

sleep();
sleep();
sleep();//打印出睡饱了

写一个afterRead函数来得到一个read函数,如下:

function afterRead(times, callbacks) {
    let arr = [];

    return function(path) {
        fs.readFile(path, 'utf8', function(err, data) {
            if(err) { throw err; }

            arr.push(data);

            if(--times === 0) {
                callbacks(arr);
            }
        });
    }
}

let read = afterRead(2, function(dataArr) {
    console.log(dataArr);
});

read('./1.txt');
read('./2.txt');//读完第二个文件打印出['./2.txt', '异步,又见异步']

Promise

需求1实现

function read(path) {
    return new Promise(function(resolve, reject) {
        fs.readFile(path, 'utf8', function(err, data) {
            if(err) { reject(err); }

            resolve(data);
        });
    });
}

read('./1.txt').then(function(data) {
    return read(data);//将产生的promise返回出去进行下一步处理
}, function(err) {
    console.log(err);
}).then(function(data) {
    console.log(data);
});

需求2实现

使用Promise.all(),能够方便进行处理,并且返回的数据是按照请求发起的顺序存放

Promise.all([read('./1.txt'), read('./2.txt')]).then(function(data) {
    console.log(data);
});

Generator + co

Generator是一个能够返回迭代器的函数,需要在function关键字和名字之间加上*以标识是个Generator,需要配合yield使用,方法每次执行到yield就会暂停,等待返回的迭代器调用next()方法继续往下执行。

next方法

返回一个包含value和done属性的对象,value表示值,done布尔类型,表示迭代是否完成。

co

co库可以自动的将generator进行迭代,同时每次迭代都会返回一个promise

模仿co库的功能

function co(it) {
    let task = it();//taskDef生成一个任务迭代器
    return new Promise(function(resolve, reject) {
        function step(data) {
            let {done, value} = task.next(data);
            if(!done) {
                value.then(function(data) {
                    step(data);
                }, reject);
            } else {
                resolve(value);
            }
        }
        step();
    });  
}

需求1实现

function *readTask() {
    let path = yield read('./1.txt');
    let content = yield read(path);

    return content;
}

co(readTask).then(function(data) {
    console.log(data); 
});

终极方案 asyn await

async和await(语法糖) === co + generator

用async 来修饰函数,aysnc需要配await,await只能promise

需求1实现

async function readTask() {
    try {
        let path = await read('./1.txt');
        let content = await read(path);

        return content;
    } catch (error) {
        throw err;
    }
}

readTask().then(function(data){
    console.log(data);
});