JS非同步解決方案的發展流程(二)

凌晨夏沫發表於2018-02-18

我們發現Promise已經可以解決了非同步程式設計問題,但是仍然不夠優雅,我們更希望編寫非同步程式碼能夠像同步程式碼一樣簡潔。

1.Generator

  • 當你在執行一個函式的時候,你可以在某個點暫停函式的執行,並且做一些其他工作,然後再返回這個函式繼續執行, 甚至是攜帶一些新的值,然後繼續執行。
  • 上面描述的場景正是JavaScript生成器函式所致力於解決的問題。當我們呼叫一個生成器函式的時候,它並不會立即執行, 而是需要我們手動的去執行迭代操作(next方法)。也就是說,你呼叫生成器函式,它會返回給你一個迭代器。迭代器會遍歷每個中斷點。
  • next 方法返回值的 value 屬性,是 Generator 函式向外輸出資料;next 方法還可以接受引數,這是向 Generator 函式體內輸入資料

1.1 generator配合promise

let fs = require('fs');
function read(file){
    return new Promise(function(resolve,reject){
        fs.readFile(file,'utf8',function(err,data){
            if(err) return reject(err);
            resolve(data);
        })
    })
}
function *gen(filename){
    // 第一次it.next().value 就是yield後面的promise
    let a = yield read(filename);
    let b = yield read(a);
    return b;
}
let it = gen('./1.txt');
// 這裡返回迭代器(迭代器返回一個物件擁有value和done屬性,當迭代完成後done的屬性為true)
it.next().value.then(function(data){
    it.next(data).value.then(function(value){
        console.log(it.next(value).value);
    })
})
複製程式碼

這裡我們手動迭代發現很複雜,所以有一個庫可以配合Generator使用,就是我們的co庫。

1.2 應用co庫

function *gen(filename){
    let a = yield read(filename);
    let b = yield read(a);
    return b;
}
let co = require('co');
co(gen('./1.txt')).then(function(data){
    console.log(data);
},function(err){
    console.log(err)
})
複製程式碼

co庫會幫我們進行自動迭代。解決了需要手動迭代的麻煩

1.3 模擬co庫

function co(gen){
    return new Promise(function(resolve,reject){
        let it = gen;
        function next(data){ // 下一次迭代時將上一次的結果傳遞進去
            let {value,done} = it.next(data);
            if(!done){
                value.then(function(data){
                    next(data); // 如果沒完成繼續迭代
                },reject)
            }
            else{
                resolve(data);
            }
        }
        next()
    });
}
複製程式碼

2.async/await

async和await就是generator和co的語法糖,使用async關鍵字,你可以輕鬆地達成之前使用生成器和co函式所做到的工作

let fs = require('fs');
function read(file){
    return new Promise(function(resolve,reject){
        fs.readFile(file,'utf8',function(err,data){
            if(err) return reject(err);
            resolve(data);
        })
    })
}
async function gen(filename){
    let a = await read(filename);
    let b = await read(a);
    return b;
}
gen('./1.txt').then(function(data){
    console.log(data)
},function(err){
    console.log(err);
});

複製程式碼

我們發現無論是generator還是async/await都離不開promise,我們在介紹幾個有關promise的庫。

3.Q庫

Q是一個在Javascript中實現promise的模組

let fs = require('fs');
let Q = require('q');
function read(){
    let defer = Q.defer(); //我們的延遲物件
    fs.readFile('1.txt','utf8',function(err,data){
        if(err) return defer.reject(err);
        defer.resolve(data);
    });
    return defer.promise;
}
read().then(function(data){
    console.log(data); 
},function(err){
    console.log(err);
});
複製程式碼

3.1 Q.all方法

與Promise.all中的用法一致

let fs = require('fs');
let Q = require('q');
function read(filename){
    let defer = Q.defer(); //我們的延遲物件
    fs.readFile(filename,'utf8',function(err,data){
        if(err) return defer.reject(err);
        defer.resolve(data);
    });
    return defer.promise;
}

Q.all([read('./1.txt'),read('./2.txt')]).then(function([a,b]){
    console.log(a,b)
});
// 這裡我們也可以利用spread將結果展開
Q.all([read('./1.txt'),read('./2.txt')]).spread(function(a,b){
    console.log(a,b)
})
複製程式碼

3.2 Q.fcall

可以通過Q.fcall創造出promise

let fs = require('fs');
let Q = require('q');
Q.fcall(function(){
    return 100;
}).then(function(data){
    console.log('result',data)
},function(err){
    console.log(err)
})
複製程式碼

4.blueBird

blueBird中有兩個常用的方法一個叫promisify另一個叫promisifyAll

let fs = require('fs');
let blueBird = require('bluebird');
let read = blueBird.promisify(fs.readFile);

read('./1.txt','utf8').then(function(data){
    console.log(data)
},function(err){
    console.log(err)
});

// 可以將非同步方法promise化,同樣也可以將所有的方法promise化,通過promisifyAll後需要在原有的函式後加上Async字尾

let fs = require('fs');
let blueBird = require('bluebird');
blueBird.promisifyAll(fs);
fs.readFileAsync('./1.txt','utf8').then(function(data){
    console.log(data)
},function(err){
    console.log(err)
});

複製程式碼

node也已經在util模組中實現了promisify方法。

4.1 實現bluebird

function promisify(fn) {
    return function (...args) {
        return new Promise(function (resolve, reject) {
            fn.call(null, ...args, function (err, data) {
                if (err) return reject(err);
                resolve(data);
            })
        })
    }
}
function promisifyAll(obj){
    Object.keys(obj).forEach(item=>{
        if(typeof obj[item] === 'function'){
            obj[item+'Async'] = promisify(obj[item])
        }
    });
}
複製程式碼

是不是迫不及待的想知道promise是如何實現的?下一節我們來Promise的實現原理,喜歡的點個贊吧^_^! 支援我的可以給我打賞哈!

JS非同步解決方案的發展流程(二)

相關文章