本文旨在前期學習前端異步過程中的筆記整理,多多指教。
1乳乌、迭代器Iterator的模擬
迭代器有一個next方法,每次執(zhí)行的時候會返回一個對象 對象里面有兩個屬性市咆,一個是value表示返回的值汉操,還有就是布爾值done,表示是否迭代完成
function buy(books){
let i = 0;
return {
next(){
let done = i == books.length;
let value = !done? books[i++]:undefined;
return {
value,
done
}
}
}
}
let it = buy(['js','node']);
var book;
do{
book = it.next()
console.log(book);
}while(!book.done);
2、生成器Generator的模擬
Generator是一個特殊的函數(shù)蒙兰,執(zhí)行它會返回一個Iterator對象磷瘤。 通過遍歷迭代器, Generator函數(shù)運行后會返回一個遍歷器對象搜变,而不是普通函數(shù)的返回值膀斋。
generator和函數(shù)的執(zhí)行流程不一樣。函數(shù)是順序執(zhí)行痹雅,遇到return語句或者最后一行函數(shù)語句就返回仰担。而變成generator的函數(shù),在每次調用next()的時候執(zhí)行绩社,遇到y(tǒng)ield語句返回摔蓝,再次執(zhí)行時從上次返回的yield語句處繼續(xù)執(zhí)行。
function *buy(books){
for(let i = 0; i < books.length;i++){
yield books[i]
}
}
let books = buy(['js','node']);
var book;
do{
book = books.next()
console.log(book);
}while(!book.done);
3愉耙、Promise
3.1 基本原理(整理from阮一峰)
#Promise對象是一個構造函數(shù)贮尉,用來生成Promise實例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise構造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是resolve和reject朴沿。它們是兩個函數(shù)猜谚,由 JavaScript 引擎提供,不用自己部署赌渣。
resolve函數(shù)的作用是魏铅,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved),在異步操作成功時調用坚芜,并將異步操作的結果览芳,作為參數(shù)傳遞出去;reject函數(shù)的作用是鸿竖,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)沧竟,在異步操作失敗時調用铸敏,并將異步操作報出的錯誤,作為參數(shù)傳遞出去悟泵。
Promise實例生成以后杈笔,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調函數(shù)。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受兩個回調函數(shù)作為參數(shù)糕非。第一個回調函數(shù)是Promise對象的狀態(tài)變?yōu)閞esolved時調用桩撮,第二個回調函數(shù)是Promise對象的狀態(tài)變?yōu)閞ejected時調用。其中峰弹,第二個函數(shù)是可選的店量,不一定要提供。這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)鞠呈。
#demo
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
timeout方法返回一個Promise實例融师,表示一段時間以后才會發(fā)生的結果。過了指定的時間(ms參數(shù))以后蚁吝,Promise實例的狀態(tài)變?yōu)閞esolved旱爆,就會觸發(fā)then方法綁定的回調函數(shù)。
3.1.1 Promise.prototype.then()
Promise 實例具有then方法窘茁,也就是說怀伦,then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態(tài)改變時的回調函數(shù)山林。前面說過房待,then方法的第一個參數(shù)是resolved狀態(tài)的回調函數(shù),第二個參數(shù)(可選)是rejected狀態(tài)的回調函數(shù)驼抹。
then方法返回的是一個新的Promise實例(注意桑孩,不是原來那個Promise實例)。因此可以采用鏈式寫法框冀,即then方法后面再調用另一個then方法流椒。
#getJSON方法(返回的是promise對象)
#參考 http://es6.ruanyifeng.com/#docs/promise
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
#上面的代碼使用then方法,依次指定了兩個回調函數(shù)明也。
#第一個回調函數(shù)完成以后宣虾,會將返回結果作為參數(shù),傳入第二個回調函數(shù)温数。
第一個then回調函數(shù)返回promise對象
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
上面代碼中绣硝,第一個then方法指定的回調函數(shù),返回的是另一個Promise對象帆吻。這時域那,第二個then方法指定的回調函數(shù),就會等待這個新的Promise對象狀態(tài)發(fā)生變化猜煮。如果變?yōu)閞esolved次员,就調用funcA,如果狀態(tài)變?yōu)閞ejected王带,就調用funcB淑蔚。
此外還有:Promise.prototype.catch() 、 Promise.prototype.finally()
3.1.2 Promise.all()
Promise.all方法用于將多個 Promise 實例愕撰,包裝成一個新的 Promise 實例刹衫。
const p = Promise.all([p1, p2, p3]);
p的狀態(tài)由p1、p2搞挣、p3決定带迟,分成兩種情況。
(1)只有p1囱桨、p2仓犬、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled舍肠,此時p1搀继、p2、p3的返回值組成一個數(shù)組翠语,傳遞給p的回調函數(shù)叽躯。
(2)只要p1、p2肌括、p3之中有一個被rejected点骑,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值谍夭,會傳遞給p的回調函數(shù)畔况。
let p1 = new Promise(function(resolve,reject){
setTimeout(() => {
resolve(100);
}, 1000);
})
let p2 = new Promise(function(resolve,reject){
setTimeout(() => {
resolve(200);
}, 2000);
})
// Promise.all 會接收一個promise數(shù)組,如果promise全部完成這個
// promise才會成功慧库,如果有一個失敗跷跪,那么這個promise就整個失敗了
console.time('cost')
// 同時異步請求多個數(shù)據(jù)的時候 會用all
Promise.all([p1,p2]).then(function(data){
console.log(data);
console.timeEnd('cost');
},function(err){
console.log(err);
})
3.1.3 Promise.race()
const p = Promise.race([p1, p2, p3]);
只要p1、p2齐板、p3之中有一個實例率先改變狀態(tài)吵瞻,p的狀態(tài)就跟著改變。那個率先改變的 Promise 實例的返回值甘磨,就傳遞給p的回調函數(shù)橡羞。
誰先回來用誰的
let p1 = new Promise(function(resolve,reject){
setTimeout(() => {
reject(100);
}, 1000);
})
let p2 = new Promise(function(resolve,reject){
setTimeout(() => {
resolve(200);
}, 2000);
})
// Promise.race 會接收一個promise數(shù)組,只要有一個成功就成功了济舆,有一個失敗就失敗了
console.time('cost')
Promise.race([p1,p2]).then(function(data){
console.log(data);
console.timeEnd('cost');
},function(err){
console.log(err);
})
3.1.4 Promise.resolve()
將現(xiàn)有對象轉為 Promise 對象
Promise.resolve('foo')
// 等價于
new Promise(resolve => resolve('foo'))
a卿泽、如果參數(shù)是一個thenable對象
thenable對象指的是具有then方法的對象,比如下面這個對象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve方法會將這個對象轉為 Promise 對象签夭,然后就立即執(zhí)行thenable對象的then方法齐邦。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
上面代碼中,thenable對象的then方法執(zhí)行后第租,對象p1的狀態(tài)就變?yōu)閞esolved措拇,從而立即執(zhí)行最后那個then方法指定的回調函數(shù),輸出 42慎宾。
b丐吓、參數(shù)不是具有then方法的對象,或根本就不是對象
如果參數(shù)是一個原始值趟据,或者是一個不具有then方法的對象券犁,則Promise.resolve方法返回一個新的 Promise 對象,狀態(tài)為resolved汹碱。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
3.2 Promise/A+規(guī)范
Promise/A+規(guī)范中文版
Promise/A+規(guī)范英文版
下面我們嘗試重寫下Promise
//Promise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REHECTED = 'rejected';
function Promise(exector){
let self = this;
self.status = PENDING;
self.value = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(value instanceof Promise){
return value.then(resolve,reject);
}
setTimeout(() => {
if(self.status == PENDING){
self.status = FULFILLED;
self.value = value;
self.onResolvedCallbacks.forEach(cb=>cb(value));
}
});
}
function reject(reason){
setTimeout(() => {
if(self.status == PENDING){
self.status = REHECTED;
self.value = reason;
self.onRejectedCallbacks.forEach(cb=>cb(reason));
}
});
}
try {
exector(resolve,reject);
} catch (e) {
reject(e);
}
}
function resolvePromise(promise2,x,resolve,reject){
// 如果x就是promise2 死循環(huán)粘衬,一直處于pending狀態(tài)
if(promise2 === x){
return reject(new TypeError('循環(huán)引用'));
}
let called = false; //promise是否已經(jīng)resolve / reject
if(x instanceof Promise){
if(x.status == PENDING){
x.then(function(y){
// 遞歸調用
resolvePromise(promise2,y,resolve,reject);
},reject);
}else{
x.then(resolve,reject);
}
// x是一個thenable對象或函數(shù) 只要有then方法的對象
} else if(x != null && ((typeof x == 'object' || typeof x == 'function'))){
try { //取then的時候可能拋異常 這里捕捉下
let then = x.then;
if(typeof then == 'function'){
// 有些Promise會同時執(zhí)行成功和失敗的回調
// 如果promise2已經(jīng)成功或者失敗了,則不要再處理了
then.call(x,function(y){
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject);
},function(err){
if(called) return;
called = true;
reject(err);
})
} else{
// 到此的話 x不是一個thenable對象那直接當成值resolve
// 比如返回是一個對象的時候:{name:'haha'比被,then:{}}會出現(xiàn)這樣的情況
resolve(x);
}
} catch (e) {
if(called) return;
called = true;
reject(e);
}
}else {
//如果x是一個普通的值色难,則用x的值resolve promise2
resolve(x);
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled:value=>value;
onRejected = typeof onRejected == 'function' ? onRejected:value=>{throw value;}
let self = this;
let promise2;
if(self.status == FULFILLED){
promise2 = new Promise(function(resolve,reject){
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
})
}
if(self.status == REHECTED){
promise2 = new Promise(function(resolve,reject){
setTimeout(() => {
try {
let x = onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
})
}
if(self.status == PENDING){
promise2 = new Promise(function(resolve,reject){
self.onResolvedCallbacks.push(function(value){
setTimeout(() => {
try {
let x = onFulfilled(value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
})
self.onRejectedCallbacks.push(function(value){
setTimeout(() => {
try {
let x = onRejected(value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e);
}
});
})
})
}
return promise2;
}
Promise.prototype.catch = function(onRejected){
this.then(null,onRejected);
}
Promise.deferred = Promise.defer = function(){
let df = {}
df.promise = new Promise(function(resolve,reject){
df.resolve = resolve;
df.reject = reject;
})
return df;
}
function gen(times,cb){
let result = [], count = 0;
return function(i,data){
result[i] = data;
if(++count == times){
cb(result);
}
}
}
// 都成功了才會成功
Promise.all = function(promises){
return new Promise(function(resolve,reject){
let done = gen(promises.length,resolve);
for(let i = 0; i < promises.length; i++){
promises[i].then(function(data){
done(i,data);
},reject)
}
})
}
// 都成功了才會成功
Promise.race = function(promises){
return new Promise(function(resolve,reject){
for(let i = 0; i < promises.length; i++){
promises[i].then(resolve,reject)
}
})
}
module.exports = Promise;
使用promises-aplus-tests對該方法進行測試
promises-aplus-tests Promise.js
下面我們簡單做下測試
let Promise = require('./Promise')
let p1 = new Promise(function(resolve,reject){
setTimeout(() => {
let num = Math.random();
if(num > .5){
resolve('ok')
}else{
reject('failed')
}
}, 1000);
})
// 測試1
// p1.then(function(data){
// console.log(data);
// },function(reason){
// console.log(reason);
// })
// 測試2
// 里面函數(shù)返回的是一個新的promise實例
// let p2 = p1.then(function(data){
// console.log(data);
// return new Promise(function(resolve,reject){
// resolve(1000); //異步成功的結果傳遞給P2的resolved
// })
// },function(err){
// console.log(err);
// return new Promise(function(resolve,reject){
// reject(2000);
// })
// })
// p2.then(function(data){
// console.log(data);
// },function(reason){
// console.log(reason);
// })
// 測試3
// p1.then('aaa').then(function(data){
// console.log(data);
// },function(err){
// console.log(err)
// })
// 測試4 循環(huán)引用
// let p2 = p1.then(function(data){
// return p2
// },function(err){
// return p2;
// })
// p2.then(function(data){
// console.log(data);
// },function(err){
// console.log(err)
// })
// 測試5 thenable 調用后返回不是函數(shù)
let p2 = p1.then(function(data){
// 不是thenable對象
return {name:'hhaa',then:{}}
},function(err){
console.log(err);
})
p2.then(function(data){
console.log(data);
},function(err){
console.log(err);
})
4、Q的使用
//
let Q = require('q');
let fs =require('fs');
function readFile(filename){
let defer = Q.defer();
fs.readFile(filename,'utf-8',function(err,data){
if(err){
defer.reject(err);
} else{
defer.resolve(data);
}
})
return defer.promise;
}
readFile('./1.txt').then(function(data){
console.log(data);
})
參照上面的調用方式等缀,簡單實現(xiàn)Q
//Q的簡單實現(xiàn)
let Q = {
defer(){
let success, error;
return {
resolve(data){
success(data);
},
reject(err){
error(err);
},
promise:{
then(onFulfilled,onRejected){
success = onFulfilled;
error = onRejected;
}
}
}
}
}
5枷莉、bluebird的使用
let Promise = require('bluebird');
let fs = require('fs');
let readFile = fs.readFile;
//可以把把一個普通的異步方法轉成返回promise的方法
let readFileSync = Promise.promisify(readFile);
readFileSync('./1.txt','utf8').then(function(data){
console.log(data);
})
//promisify的簡單實現(xiàn)
function promisify(fn){
return function(...args){
return new Promise(function(resolve,reject){
fn.apply(null,[...args,function(err,data){
err?reject(err):resolve(data);
}])
})
}
}
Promise.promisifyAll
// 會遍歷對象所有方法,然后都復制一份 對每個方法添加一個新的方法Async
Promise.promisifyAll(fs);
fs.readFileAsync('./1.txt','utf8').then(data=>console.log(data));
function promisifyAll(obj){
for(let key in obj){
if(obj.hasOwnProperty(key)&& typeof obj[key] =='function'){
obj[key+'Async'] = Promise.promisify(obj[key]);
}
}
}
6尺迂、generator + promise
let fs = require('fs');
function readFile(filename){
return new Promise(function(resolve,reject){
fs.readFile(filename,'utf-8',function(err,data){
err?reject(err):resolve(data);
})
})
}
function *read(){
console.log('start');
let a = yield readFile('./promise/1.txt');
console.log(a);
let b = yield readFile('./promise/2.txt');
console.log(b)
let c = yield readFile('./promise/3.txt');
console.log(c);
return c;
}
let it = read();//調用生成器返回迭代器
let r1 = it.next();
r1.value.then(function(data){
console.log('haha'+data);
let r2 = it.next(data);
r2.value.then(function(data){
let r3 = it.next(data);
r3.value.then(function(data){
let r4 = it.next(data);
console.log(r4);
})
})
})
7笤妙、co
基于第六條,我們這里使用co來簡化
let fs = require('fs');
let co = require('co');
function readFile(filename){
return new Promise(function(resolve,reject){
fs.readFile(filename,'utf-8',function(err,data){
err?reject(err):resolve(data);
})
})
}
function *read(){
console.log('start');
let a = yield readFile('./promise/1.txt');
console.log(a);
let b = yield readFile('./promise/2.txt');
console.log(b)
let c = yield readFile('./promise/3.txt');
console.log(c);
return c;
}
co(read);
co的原理實現(xiàn)
// co是用來自動執(zhí)行迭代器的
function co(gen){
let it = gen(); //我們讓我們的生成器持續(xù)執(zhí)行
return new Promise(function(resolve,reject){
!function next(lastVal){ //自執(zhí)行函數(shù)
let {value,done} = it.next(lastVal);
if(done){
resolve(value);
} else{
value.then(next,reject)
}
}()
})
}
8噪裕、async await
// async await 異步終極解決方案
// 他只是generator+promise的語法糖
let fs = require('fs');
function readFile(filename){
return new Promise(function(resolve,reject){
fs.readFile(filename,'utf8',function(err,data){
err?reject(err):resolve(data);
})
})
}
// 如果代碼里面有異步代碼蹲盘,前面加async,方法返回promise需要await
async function read(){
let a = await readFile('./1.txt');
console.log(a)
let b = await readFile('./2.txt');
console.log(b);
let c = await readFile('./3.txt');
console.log(c)
return 'ok';
}
read().then(data=>console.log(data))
拓展
//獲取前端返回的頁面和數(shù)據(jù)
let html = {};
let fs = require('fs');
function render(length,cb){
let html = {};
return function(key,value){
html[key]=value;
if(Object.keys(html).length == length){
cb(html);
}
}
}
let done = render(2,function(html){
console.log(html);
})
fs.readFile('./template.txt','utf8',function(err,template){
done('template',template);
})
fs.readFile('./data.txt','utf8',function(err,data){
done('data',data);
})