1.概述
javascript是單執行緒語言(單執行緒/多執行緒、阻塞/非阻塞、同步、非同步)參考此文章,所有的任務都是按順序執行的,但是對於很耗時的操作採用非同步的方式(前一個任務結束的時候,不是執行下一個任務,而是執行回撥函式,後一個任務則是不等前一個任務結束就執行)參考此文章,js中非同步程式設計的四種方法:回撥函式、事件監聽、釋出/訂閱、Promise物件,這裡討論promise的用法,promise是es6增加的內容,使得在非同步程式設計中不用過多的巢狀回撥函式,讓非同步操作變得更加簡單
2.Promise介紹
2.1 Promise建構函式
Promise建構函式接收一個函式作為引數,此函式又有兩個引數分別為resolve和reject,這兩個引數也是函式
const promise = new Promise(function(resolve, reject) {
// 非同步操作的程式碼
if(success) {
return resolve(data); // data為非同步操作成功返回的資料
} else {
return reject(error); //data為失敗時返回的資料
}
})
複製程式碼
2.2 resolve和reject
promise物件類似於一個容器(也可以說是一個狀態機),裡面包含著非同步操作,非同步操作會有兩種結果:成功或失敗。當非同步操作成功就會將pending(執行中狀態)轉為fulfilled(成功狀態)同時觸發resolve函式,用來將操作成功後得到的結果傳遞出去;當非同步操作失敗就會將pending(執行中狀態)轉為reject(拒絕狀態)同時觸發reject函式,用來將操作失敗後報出的錯誤傳遞出去
const promise = new Promise(function(resolve, reject) {
// 非同步操作的程式碼
if(success) {
return resolve(data); // data為非同步操作成功返回的資料
} else {
return reject(error); //data為失敗時返回的資料
}
})
複製程式碼
我們來看下面程式碼:
function fn1() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn1')
},1000)
})
}
function fn2() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn2')
},2000)
})
}
fn1().then(fn2)
複製程式碼
輸出為:
fn1
fn2函式不執行,這個時候我們需要呼叫resolve函式以便執行then()方法中的回撥函式fn2
function fn1() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn1')
resolve('fn1')
},1000)
})
}
複製程式碼
輸出為:
fn1
fn2
如果我們在fn1中呼叫reject函式時會出現什麼情況呢?
function fn1() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn1')
// resolve('fn1')
reject('錯誤'+'fn1')
},1000)
})
}
fn1().then(fn2).then((data) => {
console.log(data)
})
複製程式碼
輸出為:
提示錯誤未捕獲,所以需要在then()方法中新增第二個回到函式來處理出錯資訊
fn1().then(fn2).then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製程式碼
輸出為:
2.3 then()方法
當promise例項生成以後,後面跟then方法,其的第一個回撥函式來處理resolve函式傳遞的資料,第二個回撥函式來處理reject函式傳遞的資料,以上的流程用程式碼展示如下
promise
.then(function(data){
//拿到返回的資料之後做一些處理
console.log(data)
}, function(error) {
//返回失敗時做一些處理
console.log(error)
})
複製程式碼
2.3.1 then()的返回值
then()方式是Promise例項的方法,此then方法定義在原型物件(Promise.prototype)上,then()方法的返回值是一個新的Promise例項(不是原來那個Promise,原來那個Promise已經承諾過)所以我們可以採用鏈式的寫法
var p1 = new Promise( (resolve, reject) => {
setTimeout(() => resolve('p1'), 10);
});
p1.then( ret => {
console.log(ret);
return 'then1';
}).then( ret => {
console.log(ret);
return 'then2';
}).then( ret => {
console.log(ret);
});
複製程式碼
執行順序:
p1>then1>then2
從第二個then()方法開始,它們的resolve中的引數就是前一個then()中的resolve的return語句的返回值
採用鏈式的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));
複製程式碼
promise鏈式寫法如下:
function fn1() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn1')
resolve('fn1')
},1000)
})
}
function fn2() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn2')
resolve('fn2')
},2000)
})
}
function fn3() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn3')
resolve('fn3')
},3000)
})
}
function fn4() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn4')
resolve('fn4')
},4000)
})
}
fn1().then(fn2).then(fn3).then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製程式碼
依次輸出為:fn1>fn2>fn3
同時我們還可以更改執行的先後順序
function fn1() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn1')
reject(false)
},1000)
})
}
function fn2() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn2')
resolve('fn2')
},2000)
})
}
function fn3() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn3')
resolve('fn3')
},3000)
})
}
function fn4() {
return new Promise(function(resolve, reject){
setTimeout(() => {
console.log('fn4')
resolve('fn4')
},4000)
})
}
fn1().then(fn2).then(fn3).then((data) => {
console.log(data)
}, (err) => {
if(err == false){
fn3().then(fn4)
}
})
複製程式碼
輸出為:fn1>fn3>fn4
2.4 catch()方法
我們在開發時傾向於用catch()方法來處理異常,而不是在then()方法裡寫入第二個回撥函式,這種寫法類似於.then(null, rejection)
promise
.then(function(data){
//拿到返回的資料之後做一些處理
console.log(data)
})
.catch(function(error) {
//返回失敗時做一些處理
console.log(error)
}
複製程式碼
為什麼要採用這麼catch()方法呢?我們來看一個例子:
const promise = new Promise((resolve,reject) => {
console.log(1)
resolve('成功')
})
promise
.then((data) => {
console.log(data)
console.log(a)
}, (err) => {
console.log(err)
})
複製程式碼
輸出為:
1
成功
但是沒有捕捉到回撥函式裡a未定義
這個時候我們來該寫以上程式碼
1
成功
ReferenceError: a is not defined
Promise物件的Error物件具有冒泡性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲
var p = new Promise( (resolve, reject) => {
setTimeout(() => resolve('p1'), 10);
});
p.then( ret => {
console.log(ret);
throw new Error('then1');
return 'then1';
}).then( ret => {
console.log(ret);
throw new Error('then2');
return 'then2';
}).catch( err => {
// 可以捕抓到前面的出現的錯誤。
console.log(err.toString());
});
複製程式碼
輸出為:
p1
Error: then1
2.4.1 catch()返回值
既然catch()是.then(null, rejection)的別名,那麼catch()就會返回一個Promise物件,因此在後面還可以接著呼叫then方法
var p = new Promise((resolve, reject) => {
resolve(x + 2);
});
p.then( () => {
console.log('nothing');
}).catch( err => {
console.log(err.toString());
return 'catch';
}).then( ret => {
console.log(ret);
});
複製程式碼
輸出為:
ReferenceError: x is not defined
catch
當出錯時,catch會先處理之前的錯誤,然後通過return語句,將值繼續傳遞給後一個then方法中,如果沒有報錯,則跳過catch,示例如下:
var p = new Promise((resolve, reject) => {
resolve('p');
});
p.then( ret => {
console.log(ret);
return 'then1';
}).catch( err => {
console.log(err.toString());
return 'catch';
}).then( ret => {
console.log(ret);
});
複製程式碼
3. promise用法解析
3.1 用Promise實現ajax操作
const getJSON = function(url) {
const promise = new Promise(function(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 promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
複製程式碼
3.2 promise和ajax方法結合
var http = {
get: function(url) {
var promise = new Promise(function(resolve, reject) {
$.ajax({
url: url,
method: 'get',
success: function(data) {
resolve(data);
},
error: function(xhr, statusText) {
reject(statusText);
}
});
});
return promise;
}
};
http.get('data.php').then(function(data) {
document.write(data);
}, function(err) {
document.write(err);
});
複製程式碼
3.3 用promise實現非同步載入圖片的例子
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;
});
}
var loadImage1 = loadImageAsync(url);
loadImage1.then(function success() {
console.log("success");
}, function fail() {
console.log("fail");
});
複製程式碼
3.4 promise和axios方法結合
function fetch(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, params)
.then(response => {
resolve(response.data);
}, error => {
reject(error);
})
.catch(error => {
reject(error)
})
})
}
function lineStatus(params) {
return fetch('/ProductionLine/GetStatus', params)
}
function statisticsData(params) {
return fetch('/ProductionLine/GetWeightStatistics', params)
}
lineStatus(this.lineId)
.then((result) => {
this.deviceStatus.run = result.TotleMoveMotor
this.deviceStatus.stop = result.TotleStopMotor
this.deviceStatus.lost = result.TotleLoseMotor
this.deviceStatus.alarm = result.TotleAlarmMotor
this.ProductionStatus = result.ProductionStatus
console.log(result)
})
.catch((error) => {
console.log('瓦特了...(;′⌒`)')
})
statisticsData(this.lineId)
.then((result) => {
this.outputData.totalOutput = result.MainConveyorModel.OutPut
this.outputData.instantWeight = result.MainConveyorModel.InstantWeight
this.outputData.runningTime = result.MainConveyorModel.RunningTime
this.outputData.motorLoad = result.MainConveyorModel.MotorLoad
// console.log(result)
})
.catch((error) => {
console.log('瓦特了...(;′⌒`)')
})
複製程式碼
以上是Promise在開發中常見的用法,參考了以下幾篇文章,特此感謝作者