寫在前面
異步編程對Javascript語言非常重要,在Javascript的發(fā)展道路上蚊惯,異步編程的方法也是一直在不斷更新。關(guān)于這方面的知識,網(wǎng)上已經(jīng)有很多成熟的教程和講解瑟由,我將對這些教程進行整理和歸納,整理出異步JS異步編程的幾種解決方法冤寿。
Javascript的異步執(zhí)行
Javascript的執(zhí)行環(huán)境是單線程的歹苦,所謂的單線程就是一次只能完成一個任務(wù),其任務(wù)的調(diào)度方式就是排隊督怜,這就和超市等待付款一樣殴瘦,前面的人還沒有結(jié)賬,后面的人就只能等著号杠。這種模式的壞處很明顯蚪腋,就是如果有一個任務(wù)耗時很長,就會拖延整個程序的執(zhí)行。為了解決這個問題屉凯,Javascript將任務(wù)的執(zhí)行模式分為兩種:同步(synchronous)和異步(Asynchronous)动遭。
- 同步模式就是前面提到的這種,后一個任務(wù)等待前一個任務(wù)結(jié)束再執(zhí)行神得,程序執(zhí)行的順序與任務(wù)排列的順序是一致的,同步的偷仿。
- 異步模式哩簿,通俗點說,就是前面排隊的人告訴后面排隊的一個準確時間酝静,這樣后面的人就可以利用這段時間去干些別的事情节榜。異步模式可以通過回調(diào)函數(shù)實現(xiàn),假設(shè)我們需要進行一個耗時的數(shù)據(jù)請求别智,在對外部數(shù)據(jù)發(fā)送請求后宗苍,程序的執(zhí)行權(quán)將會交給別的任務(wù),一直等到外部數(shù)據(jù)返回以后薄榛,系統(tǒng)才會通知執(zhí)行回調(diào)函數(shù)讳窟,而回調(diào)函數(shù)中通常會包括對獲取的數(shù)據(jù)的處理方法。所以敞恋,在這個模式下丽啡,程序的執(zhí)行順序和任務(wù)的排列順序是不一致的,異步的硬猫。
Promise
Promise是異步編程的一種解決方案补箍,ES6原生提供了Promise對象。
特點
- Promise對象有三種狀態(tài):Pending(進行中)啸蜜,Resolved(已完成)坑雅,Rejected(已失敗),只有異步操作的結(jié)果可以決定當前是哪種狀態(tài)衬横。
- Promise的狀態(tài)改變可能有兩種情況:pending->Resolved或者pending->Rejected,一旦發(fā)生裹粤,狀態(tài)就會保持不變。
基本用法
- Promise對象是一個構(gòu)造函數(shù)冕香,用來生成Promise實例蛹尝。
- Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別為resolve和reject,他們是兩個函數(shù)悉尾,由JS引擎提供突那,不用自己部署。
- resolve函數(shù)在異步操作成功時調(diào)用构眯,并將異步操作的結(jié)果愕难,作為參數(shù)傳遞出去。
- reject函數(shù)在異步操作失敗時調(diào)用,并將異步操作報出的錯誤猫缭,作為參數(shù)傳遞出去葱弟。
- then方法可以接受兩個回調(diào)函數(shù)作為參數(shù),第一個回調(diào)函數(shù)時Promise對象的狀態(tài)變?yōu)閞esolved時候調(diào)用猜丹,第二個是Promise對象狀態(tài)變?yōu)閞eject時調(diào)用芝加,第二個函數(shù)可選。
- 如果Promise對象狀態(tài)變?yōu)閞eject時射窒,會調(diào)用catch方法指定的回調(diào)函數(shù)處理這個錯誤藏杖,所以,除了定義then的第二個參數(shù)以外脉顿,也可以定義catch方法的回調(diào)函數(shù)來處理Promise拋出的錯誤蝌麸。
例1 (簡單例子)
function test(resolve,reject){
var timeout=Math.random()*2;
console.log('設(shè)置timeout為'+timeout+'秒');
setTimeout(function () {
if(timeout<1){
console.log('call resolve()');
resolve(" resolved");
}else {
console.log('call reject()');
reject(" rejected");
}
}, timeout*1000);
}
//寫法1
var p1=new Promise(test);
p1.then(function(result){
console.log("成功"+result);
},function(result){
console.log("失敗"+result);
});
//寫法2
var p1=new Promise(test);
var p2=p1.then(function(result){
console.log("成功"+result);
});
var p3=p2.catch(function(result){
console.log("失敗"+result);
})
/*
輸出:
設(shè)置timeout為1.3983493377635763秒
call reject()
失敗 rejected
或者:
設(shè)置timeout為0.8257771710631485秒
call resolve()
成功 resolved
*/
這個例子中p1是promise實例,方法一和方法二所表達的意思是一樣的艾疟,只是寫法不同来吩,方法1用的就是上面說過的給then傳遞兩個回調(diào)函數(shù)作為參數(shù)的寫法。方法二只將then用于函數(shù)執(zhí)行成功的情況蔽莱,而使用catch來處理函數(shù)執(zhí)行不成功的情況弟疆。一般傾向于使用catch方法定義reject狀態(tài)的回調(diào)函數(shù)。
上述的test函數(shù)執(zhí)行成功的情況下碾褂,即隨機數(shù)小于1的情況下兽间,我們將調(diào)用resolve(' resolved')
,在執(zhí)行失敗的情況下正塌,將調(diào)用reject(" rejected")
嘀略。變量p1是一個Promise對象,負責執(zhí)行test函數(shù)乓诽。當test函數(shù)執(zhí)行成功時帜羊,then方法指定的回調(diào)函數(shù),將在當前腳本所有同步任務(wù)執(zhí)行完后執(zhí)行鸠天,也就是過了隨機數(shù)產(chǎn)生的秒數(shù)以后執(zhí)行這里的console.log("成功"+result)
讼育。相同的,當test函數(shù)執(zhí)行失敗時稠集,catch方法中指定的回調(diào)函數(shù)也會在test中的所有任務(wù)執(zhí)行完后執(zhí)行奶段。
上面提到的console.log("成功"+result)
中的result其實就是等于" resolved",因為如果resolve函數(shù)和reject函數(shù)帶有參數(shù)剥纷,那么它們的參數(shù)就會被傳遞給回調(diào)函數(shù)痹籍。通常情況下,reject函數(shù)的參數(shù)會使Error對象的實例晦鞋,表示拋出的錯誤蹲缠;resolve函數(shù)的參數(shù)可能是一個正常值棺克,也有可能是另一個Promise實例,表示異步操作的結(jié)果可能是另一個異步操作线定。接下來就舉一個執(zhí)行若干個異步任務(wù)的例子.
例2 (若干個異步任務(wù))
function add(input){
return new Promise(function(resolve,reject){
var result=input+input;
console.log("add");
setTimeout(function(){
if(result<1000){
resolve(result);
}else {
reject(result);
}
},500);
})
}
function multiply(input){
return new Promise(function(resolve,reject){
var result=input*input;
console.log("multiply");
setTimeout(resolve,500娜谊,result);
})
}
var p=new Promise(function(resolve,result){
console.log("start new promise");
resolve(10);
})
p.then(add).then(multiply).then(add).then(add).then(function(result){
console.log("小于1000"+result);
},function(result){
console.log("大于1000"+result);
});
//輸出:大于1000,1600
上面這段代碼首先定義了add和multiply斤讥,然后在add()函數(shù)中定義了成功和失敗條件纱皆,就是當數(shù)字小于1000的時候未成功,大于1000的時候為失敗芭商。這里最重要的點是add和multiply都會返回一個Promise對象抹剩,所以在第一個Promise對象p運行結(jié)束后,會開始調(diào)用函數(shù)add蓉坎,并將10傳遞給函數(shù)add當作input,此時multiply方法數(shù)就會等待add返回的這個新的promise對象發(fā)生變化胡嘿,依次類推蛉艾,最后的一個then方法指定的回調(diào)函數(shù)就會等待最后一個add返回的新的promise對象狀態(tài)發(fā)生變化,如果變?yōu)镻romise對象的狀態(tài)為成功衷敌,就會調(diào)用console.log("小于1000"+result);
勿侯,反之調(diào)用console.log("大于1000"+result);
例3 (all和race)
all方法將用于多個Promise實例,包裝成一個新的Promise實例缴罗。
var p=Promise.all([p1,p2,p3]);
在使用all的情況下助琐,p的狀態(tài)由p1,p2,p3決定:
- 只有p1,p2,p3的狀態(tài)都變成Resolved,p的狀態(tài)才會變成Resolved面氓。
- p1,p2,p3中只要有一個被Rejected兵钮,p的狀態(tài)就變成了Rejected。
var p1=new Promise(function(resolve,reject){
setTimeout(resolve,1000,"p1 finish in 1s");
});
var p2=new Promise(function(resolve,reject){
setTimeout(resolve,2000,"p2 finish in 2s");
});
Promise.all([p1,p2]).then(function(result){
console.log("end"+result);
});
//直到2s后'p1 finish in 1s'和‘p2 finish in 2s’會被同時輸出
上例中舌界,因為只有p1,p2都為Resolved掘譬,由它們包裝成的新的Promise實例的狀態(tài)才會變?yōu)镽esolved,所以直到2秒以后呻拌,p1和p2的返回值才會被同時傳遞給新的promise實例的回調(diào)函數(shù)葱轩。
race方法和all類似,同樣將多個Promise實例包裝成一個新的Promise實例藐握。
var p=Promise.race([p1,p2,p3]);
在使用race的情況下靴拱,只要p1,p2,p3之中有一個率先改變狀態(tài),p的狀態(tài)就跟著改變猾普。那個率先改變的Promise實例的返回值袜炕,就傳遞給p的回調(diào)函數(shù)。
var p1=new Promise(function(resolve,reject){
setTimeout(resolve,1000,"p1 finish in 1s");
});
var p2=new Promise(function(resolve,reject){
setTimeout(resolve,2000,"p2 finish in 2s");
});
Promise.race([p1,p2]).then(function(result){
console.log("end"+result);
});
//1秒后'p1 finish in 1s'和‘p2 finish in 2s’就會被同時輸出
上例中因為p1執(zhí)行的較快抬闷,它的狀態(tài)會率先變?yōu)镽esolved妇蛀,所以p1的返回值耕突,就傳遞給了新的promise實例的回調(diào)函數(shù)。
Promise應用于Ajax
使用Promise簡化Ajax異步處理:
function ajax(method,url,data){
var request=new XMLHttpRequest();
return new Promise(function(resolve,reject{
request.onreadystatechange=function(){
if(request.readyState==4){
if(request.status==200){
resolve(request.responseText);
}else {
reject(request.status);
}
}
});
request.open(method,url);
request.send(data);
})
}
var p=ajax('POST',someUrl);
p.then(function(text){
alert(text);//成功獲取到數(shù)據(jù)
}).catch(function(status){
alert(status); //請求數(shù)據(jù)失敗评架,獲得相應代碼
});
這里的method可以是'GET'或者'POST',url是php或者asp文件的地址眷茁。
總結(jié)
關(guān)于Promise的大部分知識已經(jīng)在本文涵蓋到,不得不說廖雪峰和阮一峰老師的講解很全面纵诞,但也許有些同學對于ES6并不熟悉上祈,直接看阮一峰的Promise這章會有點吃力,所以筆者盡可能地在他們的基礎(chǔ)上解釋地更加細致一些浙芙,當然登刺,想要全面地了解Promise的所有內(nèi)容,還是要靜下心來閱讀ES6入門這本書嗡呼。這是JS異步的第一篇纸俭,接下來仍會介紹別的JS異步的解決方案。