非同步程式設計方案進化論

槑有人用發表於2018-03-15

非同步方案進化過程: 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);
});
複製程式碼

相關文章