我們發現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的實現原理,喜歡的點個贊吧^_^! 支援我的可以給我打賞哈!