Promise物件和async函式
非同步程式碼
現在常見的的非同步程式碼
- 定時器
- ajax 請求
注意事項
1.在 JavaScript 中,記住一件事兒:所有的非同步回撥函式執行一定在普通程式碼執行之後
2.如果想要獲取非同步程式碼的執行結果:通過回撥函式來接收
基於回撥函式的非同步流程控制
封裝一個原生 get 請求
function get(url, cd) {
const xhr = new XMLHttpRequest();
xhr.open("get", url);
xhr.send();
xhr.onload = function () {
cd(this.response);
};
}
原生 ajax 請求注意事項
事件最好放在傳送請求前 不然像捕獲傳送失敗這種函式會獲不到
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
resolve(this.response);
});
xhr.addEventListener("error", function (err) {
reject(err);
});
xhr.open("get", url);
xhr.send();
非同步並行
- 一起執行 不分先後順序
get("http://jsonplaceholder.typicode.com/posts", function (res) { console.log(1); }); get("http://jsonplaceholder.typicode.com/comments", function (res) { console.log(2); }); get("http://jsonplaceholder.typicode.com/users", function (res) { console.log(3); });
非同步序列
- 依次執行 上一層執行完畢在執行下面程式碼
get("http://jsonplaceholder.typicode.com/posts", function (res) {
console.log(1);
get("http://jsonplaceholder.typicode.com/comments", function (res) {
console.log(2);
get("http://jsonplaceholder.typicode.com/users", function (res) {
console.log(3);
});
});
});
回撥地獄
非同步序列如果巢狀過深會造成經典的回撥地獄
axios 的序列
axios({
method: "GET",
url: "http://jsonplaceholder.typicode.com/posts",
})
.then((res) => {
console.log("2 posts 的響應結果");
return axios({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
});
})
.then((res) => {
console.log("3 users 的響應結果");
return axios({
method: "GET",
url: "http://jsonplaceholder.typicode.com/comments",
});
})
.then((res) => {
console.log("4 comments 的響應結果");
});
補充:setTimeout、setInterval 被遺忘的第三個引數
MDN介紹
定時器啟動時候,第三個以後的引數是作為第一個func()
的引數傳進去。
var sum = function (x, y, z) {
console.log(x + y + z); // 列印6
};
setTimeout(sum, 1000, 1, 2, 3);
Promise 物件
Promise 的含義
Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
物件。
所謂Promise
,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。
Promise
物件有以下兩個特點。
(1)物件的狀態不受外界影響。Promise
物件代表一個非同步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise
物件的狀態改變,只有兩種可能:從pending
變為fulfilled
和從pending
變為rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise
物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
注意,為了行文方便,本章後面的resolved
統一隻指fulfilled
狀態,不包含rejected
狀態。
有了Promise
物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise
物件提供統一的介面,使得控制非同步操作更加容易。
Promise
也有一些缺點。首先,無法取消Promise
,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise
內部丟擲的錯誤,不會反應到外部。第三,當處於pending
狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
如果某些事件不斷地反覆發生,一般來說,使用 Stream 模式(node.js)是比部署Promise
更好的選擇。
基本用法
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 非同步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve
和reject
。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。
resolve
函式的作用是,將Promise
物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject
函式的作用是,將Promise
物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。
Promise
例項生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回撥函式。
then
方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise
物件的狀態變為resolved
時呼叫,第二個回撥函式是Promise
物件的狀態變為rejected
時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise
物件傳出的值作為引數。
下面是一個Promise
物件的簡單例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, "done");
});
}
timeout(100).then((value) => {
console.log(value);
});
上面程式碼中,timeout
方法返回一個Promise
例項,表示一段時間以後才會發生的結果。過了指定的時間(ms
引數)以後,Promise
例項的狀態變為resolved
,就會觸發then
方法繫結的回撥函式。
Promise-then 方法
- then 方法執行完以後會返回一個新的 Promise 物件
- 如果是普通資料,那麼它會把該資料包裝為那個返回的 Promise 的 resolve 結果
- 如果你返回的資料就是一個 Promise 物件,那它就不做任何處理了
Promise.all
* Promise.all()方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。
* 所有引數的狀態都變成fulfilled,res的狀態才會變成fulfilled
Promise.race
* Promise.race()方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項。
* 引數之中有一個例項率先改變狀態,res的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式
Promise.allSettled
* Promise.allSettled()方法接受一組 Promise 例項作為引數,包裝成一個新的 Promise 例項。
* 只有等到所有這些引數例項都返回結果,不管是fulfilled還是rejected,包裝例項才會結束。
Promise.any
* Promise.any()方法。該方法接受一組 Promise 例項作為引數,包裝成一個新的 Promise 例項返回。
* 只要引數例項有一個變成fulfilled狀態,包裝例項就會變成fulfilled狀態;
async 函式
Async 函式簡化了 Promise 的呼叫,本質還是 Promise,有點類似 promise 的 then 方法
任何函式都可以被標記為 async 函式
// 具名函式:async function main() {
// 匿名函式:async function () {}
// 箭頭函式:async () => {}
// 物件函式成員簡寫:
// const user = {
// sayHello: async function () {}
// sayHello: async () => {}
注意物件裡函式的簡寫 async寫在方法名的前面 其他的放在函式前即可
// async sayHello () {}
// }
async 函式-返回值
- async 函式始終返回 Promise
- async 函式的返回值
- 如果是普通資料,則直接把它包裝到 promise 物件中,資料就是 resolve 的結果
- 如果你返回的直接就是一個 promise 物件,則不作任何處理
- 如果你什麼都沒有反回,則返回一個空的 promise 物件
await
await 代表等待的意思,就是等待後面的 promise 執行完返回後 在執行後續程式碼 ,相當於將後面的程式碼改成同步執行
async function ayrequest() {
const res = await request("http://jsonplaceholder.typicode.com/users");
console.log(1); //此程式碼會等待上面程式碼返回後再執行
request("http://jsonplaceholder.typicode.com/users");
console.log(2); // 此程式碼不會等待上面程式碼返回 , 直接執行
}
async 函式-異常處理
跟推薦使用 try catch 語法來獲取異常
async function ayrequest() {
const res = await request("http://jsonplaceholder.typicode.com/users");
return request("http://jsonplaceholder.typicode.com/posts");
// 異常處理
try {
console.log(1);
const res = await request("sdf://dsfsdfsdf.sdf.com/dsf");
console.log(2);
console.log(res);
} catch (error) {
console.log(3);
console.log("傳送失敗", error);
}
}
在 Vue 中使用 async 函式
async created () {
axios({
method: 'GET',
url: 'http://jsonplaceholder.typicode.com/posts'
}).then(res => {
console.log(res)
})
}
利用Promise + aysnc 實現對原生ajax的封裝
這裡利用 module 的 頂端可以直接使用 await 的特性
<script type="module">
function get(url) {
// 返回一個封裝的 Promise
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", res => {
let reseult = JSON.parse(res.target.response || "{}");
// 成功resolve返回結果 狀態變為 fulfilled (已成功)
resolve(reseult);
});
xhr.addEventListener("error", err => {
// 失敗reject返回錯誤 狀態變為 rejected (已失敗)
reject(err);
});
xhr.open("get", url);
xhr.send();
});
}
// 使用 await 等待 Promise的 返回值
let res = await get("http://localhost:8848/getSysDB");
</script>
使用Promise的注意事項
將多個Promise包裝成一個Promise的方法 ,都是並行執行
- Promise.all
- Promise.race
- Promise.allSettled
- Promise.any
這幾個方法都用於將Promise 例項作為引數,包裝成一個新的 Promise 例項返回
這幾個方法在執行的 Promise 時候,都是並行執行
注意:如果有先執行完某個Promise 在執行某個 Promise 的時候不可以這幾種方法,需要改序列執行(一個執行完再執行另一個)