1.Promise 的含義
Promise是異步編程的一種解決方案,比傳統的解決方案- -回調函數和事件- - 更合理和更強大。它由社區最早提出和實現,ES6將其寫進入了語言標準,統一了用法,原生提供了Promise對象。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的時間(通常是一個異步操作)的結果。從語法上說,Promise是一個對象,從它可以獲取異步操作的消息。Promise提共統一的API,各種異步操作都可以用同樣的方法進行處理。
Promise對象有以下各個特點。
(1) 對象的狀態不受外界的影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,熱和其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會在改變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected.只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時候稱為resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,乳溝你錯過了它,再去監聽,是得不到結果的。
Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應發哦外部。第三,當初與pending狀態時,無法得到目前進展到哪一個階段。
如果某些事件不斷地地反復發生,一般來說,使用Stream模式是比部署Promise更好的選擇
2.基本用法
ES6規定,Promise對象是一個構造函數,用來生成Promise實例。
下面diamante創造了一個Promise實例
const promise = new Promise(function(resolve,reject){
if(/*異步操作成功*/){
resolve(value);
} else {
reject(error);
}
});
Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,有javascript引擎提供,不用自己部署。
resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。
Promise實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。
promise.then(function(value) {
? // success
}, function(error) {
? // failure
});
then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved時調用,第二個回調函數是Promise對象的狀態變為rejected時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作為參數。
function timeout(ms) {
return new Promise((resolve,reject) => {
setTimeout(resolve,ms,'done');
});
}
timeout(100).then((value) =>{
console.log(value);
});
上面代碼中,timeout方法返回一個Promise實例,表示一段時間以后才會發生的結果。過了指定的時間以后,Promise實例的狀態變為resolved,就會觸發then方法綁定的回調函數。
Promise新建后就會立即執行。
let promise = new Promise(function(resolve, reject) {
? console.log('Promise');
? resolve();
});
promise.then(function() {
? console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
上面代碼中,Promise 新建后立即執行,所以首先輸出的是Promise。然后,then方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行,所以resolved最后輸出。
下面是異步加載圖片的例子。
function loadImageAsync(url) {
return new Promise(function(resolve,reject){
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at' + url));
};
image.src = url;
});
}
上面代碼中,使用Promise包裝了一個圖片加載的異步操作。如果加載成功,就調用resolve方法,否則就調用reject方法。
下面是一個用Promise對象實現的Ajax操作的例子。
const getJSON = function(url) {
const promise = new Promise(funciton(resolve,reject){
const handler = function() {
? ? ? if (this.readyState !== 4) {
? ? ? ? return;
? ? ? }
? ? ? if (this.status === 200) {
? ? ? ? resolve(this.response);
? ? ? } else {
? ? ? ? reject(new Error(this.statusText));
? ? ? }
? ? };
? ? const client = new XMLHttpRequest();
? ? client.open("GET", url);
? ? client.onreadystatechange = handler;
? ? client.responseType = "json";
? ? client.setRequestHeader("Accept", "application/json");
? ? client.send();
});
return promis;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents:' + json);
},function(error){
console.log('出錯了',error)
})
3. Promise.prototype.then()
Promise實例具有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為Promise實例添加狀態改變時的回調函數。
前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then方法后面再調用另一個then方法。
getJSON("/post.json").then(function(json){
return json.post;
}).then(function(post){
//...
})
上面代碼使員工then方法,以此指定了兩個回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數。
采用鏈式的then,可以指定一組按照次序調用函數。這時,前一個回調函數,有可能返回的還是一個Promise對象(即有異步操作),這時后一個回調函數,就會等待該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方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。如果變為resolved,就調用funcA,如果狀態變為rejected,就調用funcB。
如果采用箭頭函數,上面的代碼可以簡寫的更加簡潔
getJSON("/post/1.json").then(
post => getJSON(post.commentURL);
).then(
comments => console.log("resolved: ", comments),
? err => console.log("rejected: ", err)
);
4.自我理解關于ES6
Promise——承諾
異步:操作之間沒有關系,各自完成各自的,可以同時進行多個操作;
同步:操作之間是相關的,同時只能完成一個操作,必須等前一步完成,才能進行下一步
異步:代碼復雜,操作困難,提高性能
同步:代碼簡單
Promise——消除異步操作
*用同步一樣的方式,來消除異步操作
Promise.all
Promise.race 競速
Promise.race([
$.ajax({url:"http:www.baidu.com"}),
$.ajax({url:"http:www.baidu.com"}),
$.ajax({url:"http:www.baidu.com"}),
$.ajax({url:"http:www.baidu.com"})
]);
eg:
let p = new Promise(function(resolve,reject){
//異步執行
//resolve——成功了
//reject——失敗了
});
Promise.all([
? ? p1,p2
? ? ]).then(function(arr){
? ? ? alert("全部都成功了");
? ? ? let [res1,res2] = arr;
? ? ? console.log(res1);
? ? ? console.log(res2);
? ? },function(){
? ? ? alert("至少有一個成功了");
? ? })
注意:ajax不能再file文件路徑下支持
jquery自己的Promise
有了Promise之后的異步:
Promise.all([$.ajax(),$.ajax()]).then(results=>{
//對了
},err=>{
//錯了
});
eg:
? ? Promise.all([
? ? ? $.ajax({url:"../js/testJson.json",dataType:"json"}),
? ? ? $.ajax({url:"../js/testJson2.json",dataType:"json"}),
? ? ]).then(function(results){
? ? ? let [res1,res2] = results;
? ? ? console.log(res1);
? ? ? console.log(res2);
? ? },function(err){
? ? ? console.log(err)
? ? });
自己封裝Promise自己調用
? ? Promise.all([
? ? ? createPromise("../js/testJson2.json"),
? ? ? createPromise("../js/testJson.json")
? ? ]).then(function(arr){
? ? ? alert("全部都成功了");
? ? ? let [res1,res2] = arr;
? ? ? console.log(res1);
? ? ? console.log(res2);
? ? },function(){
? ? ? alert("至少有一個成功了");
? ? })
? ? function createPromise(url){
? ? ? return new Promise(function(resolve,reject){
? ? ? ? $.ajax({
? ? ? ? ? url:url,
? ? ? ? ? dataType:'json',
? ? ? ? ? success(arr){
? ? ? ? ? ? resolve(arr);
? ? ? ? ? },
? ? ? ? ? err(err){
? ? ? ? ? ? reject(err);
? ? ? ? ? }
? ? ? ? })
? ? ? });
? ? }
原文:阮一峰 ES6