深入理解javascript系列(十九):從Promise開始到async/await
什麼是同步與非同步的定義,在這裡我就不做記錄,直接用程式碼來表示它們之間的區別。
首先使用Promise模擬一個發起請求的函式,該函式執行後,會在1s之後返回數值30。
function fn() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(30);
}, 1000);
})
在該函式的基礎上,我們也可以使用async/await語法來模擬同步效果。
var foo = async function() {
var t = await fn();
console.log(t);
console.log(`next`);
}
foo();
輸出結果為:
Promise {<pending>} //1s 之後依次輸出
test:11 30
test:12 next
而非同步效果則會有不同的輸出結果:
var foo = function() {
fn().then(function(res) {
console.log(res);
});
console.log(`next`);
}
輸出結果:
next
// 1s後
30
好了,接下來我們正式開始記錄Promise
Promise
1. Ajax
Ajax是網頁與服務端進行資料互動的一種技術。我們可以通過服務端提供的介面,用Ajax向服務端請求我們需要的資料。過程如下:
//簡單的Ajax原生實現
//服務端介面
var url = `api/xxxx`;
var result;
var XHR = new XMLHttpRequest();
XHR.open(`GET`, url, true);
XHR.send();
XHR.onreadystatechange = function() {
if(XHR.readyState == 4 && XHR.status == 200) {
result = XHR.response;
}
}
這樣看上去並沒有什麼問題。但是如果這個時候,還需要做另一個Ajax請求,那麼這個新的Ajax請求中的一個引數,則必須從上一個Ajax請求中獲取,這個時候我們就不得不就得在result得到後在進行一次請求。
當第三個Ajax(甚至更多)仍然依賴上一個請求的時候,此時的程式碼就變成了一場災難。我們需要不停地巢狀回撥函式,以確保下一個介面所需要的引數的正確性,這樣的災難,我們稱為回撥地獄。
所以隨著發展,就出現了Promise,他能解決這個問題。
我們想要確保某程式碼在某某之後執行時,可以利用函式呼叫棧,將想要執行的程式碼放入回撥函式中(這是利用同步阻塞)。
function a(callback) {
console.log(`先結婚`)
callback();
}
function b() {
console.log(`再生孩子`)
}
a(b);
插個題外話:“瀏覽器最早內建的setTimeout與setInterval就是基於回撥的思想實現的”。
但是這裡也有一個問題,我們想要在a中執行的程式碼必須現在callback之前才能輸出我們想輸出的。那該怎麼辦?
其實問題很好解決,除了利用函式呼叫棧的執行順序外,還可以利用佇列機制來確保我們想要的程式碼壓後執行。
function a(callback) {
//將想要執行的程式碼放入佇列中後,根據事件迴圈機制,
//就不用把它放到最後面了。
callback && setTimeout(callback, 0);
console.log(`先結婚`)
}
function b() {
console.log(`再生孩子`)
}
a(b);
與setTimeout類似,Promise也可以認為是一種任務分發器,它將任務分配到Promise佇列中,通常的流程是首先發起一個請求,然後等待(等待時間沒法確定)並處理請求結果。
var tag = true;
var p = new Promise(function(resolve, reject) {
if(tag) {
resolve(`tag is true`)
} else {
reject(`tag is false`)
}
})
p.then(function(result) {
console.log(result);
})
.catch(function(err) {
console.log(err);
})
下面簡單介紹一下Promise的相關基礎知識:
- new Promise表示建立一個Promise例項物件。
- Promise函式中的第一引數為一個回撥函式,也可以稱之為executor。通常情況下,在這個函式中,會執行發起請求操作,並修改結果的狀態值。
- 請求結果有三種狀態,分別是pending(等待中,表示還沒有得到結果)、resolved(得到了我們想要的結果,可以繼續執行),以及rejected(得到了錯誤的,或者不是我們期望的結果,拒絕繼續執行)。請求結果的預設狀態為pending。在executor函式中,可以分別使用resolve與rejected將狀態修改為對應的resolved與rejected。resolve、reject是executor函式的兩個引數,它們能夠將請求結果的具體資料傳遞出去。
- Promise例項擁有的then方法,可以用來處理當請求結果的狀態變成resolved時的邏輯。then的第一個引數為一個回撥函式,該函式的引數是resolve傳遞出來的資料。在上面的例子中,result = tag is true。
- Promise例項擁有的catch方法,可用來處理當前請求結果的狀態變成rejectd時的邏輯。catch的第一個引數為一個回撥函式,該函式的引數是一個reject傳遞出來的資料。在上面的例子中,err = tag is false。
下面通過例子來感受一下Promise的用法。
//demo01.js
function fn(num) {
//建立一個Promise例項
return new Promise(function(resolve, reject) {
if(typeof num == `number`) {
//修改結果狀態值為resolved
resolve();
} else {
// 修改結果狀態值為rejected
reject();
}
}).then(function() {
console.log(`引數是一個number值`);
}).catch(function() {
console.log(`引數不是一個number值`);
})
}
//修改引數的型別,觀察輸出的結果
fn(`12`);
//注意觀察該語句的執行順序
console.log(`next code`);
then方法可以接收兩個引數,第一個引數用來處理resolved狀態的邏輯,第二個引數用來處理rejected狀態的邏輯。
then方法因為返回的仍是一個Promise例項物件,因此then方法可以巢狀使用。在這個過程中,通過在內部函式末尾return的方式,能夠將資料持續往後傳遞。
下面我們來對Ajax進行一個簡單的封裝。
var url = `api/xxxx`;
//封裝一個get請求的方法
function getJSON(url) {
return new Promise(function(resolve, reject) {
//利用Ajax傳送一個請求
var XHR = new XMLHttpRequest();
XHR.open(`GET`, url, true);
XHR.send();
//等待結果
XHR.onreadystatechange = function() {
if(XHR.readyState == 4) {
if(XHR.status == 200) {
try {
var res = JSON.parse(XHR.responseText);
// 得到正確的結果修改狀態並將資料傳遞出去
resolve(response);
} catch(e) {
reject(e)
}
} else {
// 得到錯誤的結果並丟擲異常
reject(new Error(XHR.statusText));
}
}
}
})
}
//封裝好以後,使用就很簡單了
getJSON(url).then(function(res){
console.log(res)
})
2. Promise.all
當有一個Ajax請求,它的引數需要另外兩個甚至更多個請求都有返回結果之後才能確定時,就需要用到Promise.all來幫助我們應對這個場景。
Promise.all接收一個Promise物件組成的陣列作為引數,當這個陣列中所有的Promise物件狀態都變成resolved或者rejected時,它才會去呼叫then方法。
var url1 = `xxx`;
var url2 = `xxxxx`;
function renderAll() {
return Promise.all([getJSON(url1), getJSON(url2)]);
}
renderAll().then(function(value) {
console.log(value);
})
3. Promise.race
與Promise.all相似的是,Promise.race也是一個Promise物件組成的陣列作為引數,不同的是,只要當陣列中的其中一個Promise狀態變成了resolved或者rejected時,就可以呼叫then方法。
async/await
非同步問題不僅可以用Promise,還可以用async/await,都說這是終極解決方案。
async/await是ES7中新增的語法,雖然現在有些瀏覽器已經支援了該語法,但在實際使用中,仍然需要在構建工具中配置對該語法的支援才能放心使用。
在函式宣告的前面,加上關鍵字async,這就是async的具體使用。
async function fn() {
return 30;
}
//或者
const fn = async ()=> {
return 30;
}
console.log(fn());
//列印結果
Promise {<resolved>: 30}__proto__:Promise[[PromiseStatus]]:"resolved"[[PromiseValue]]:30
可以發現列印結果是一個Promise物件,因此可以猜到async其實是Promise的一個語法糖,目的是為了讓寫法更加簡單,因此也可以使用Promise的相關語法來處理後續的邏輯。
fn().then(res=>{
console.log(res);
})
await的含義是等待,意思就程式碼需要等待await後面的函式執行完並且有了返回結果之後,才繼續執行下面的程式碼。這正是同步的效果。
但是需要注意的是,await關鍵字只能在async函式中使用,並且await後面的函式執行後必須返回一個Promise物件才能實現同步的效果。
當使用一個變數去接收await的返回值時,該返回值為Promise中resolve傳遞出來的值,也就是PromiseValue。
為了切實感受下async/await的用法。我們結合實際開發中最常遇到的非同步請求介面的場景。
//先定義介面請求的方法,由於jQuery封裝的幾個請求方法都是返回Promise例項。
//因此可以直接使用async/await函式實現同步
const getUserInfo = () => $.get(`api/asdsd`);
const clickHandler = async ()=>{
try{
const res = await getUserInfo();
console.log(res);
// do something
} catch(e){
//處理錯誤邏輯
}
為了保證邏輯的完整性,在實踐中try/catch必不可少。
原文釋出時間為:2018年06月21日
原文作者:Panthon
本文來源: 掘金 如需轉載請聯絡原作者
相關文章
- 深入理解 JavaScript 非同步系列(5)—— async awaitJavaScript非同步AI
- 重構:從Promise到Async/AwaitPromiseAI
- 深入理解 promise、generator+co、async/await 用法PromiseAI
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- 幫助你開始理解async/awaitAI
- 回撥函式到promise再到理解async/await函式PromiseAI
- 深入理解Promise從這裡開始Promise
- 深入理解 async / awaitAI
- Promise && async/await的理解和用法PromiseAI
- 理解JavaScript的async/awaitJavaScriptAI
- 理解 JavaScript 的 async/awaitJavaScriptAI
- 理解koa2 之 async + await + promiseAIPromise
- Promise, Generator, async/await的漸進理解PromiseAI
- JavaScript 的 async/await 理解(4)JavaScriptAI
- 深入理解javascript系列(一):從三種資料結構開始JavaScript資料結構
- JavaScript非同步程式設計史:回撥函式到Promise到Async/AwaitJavaScript非同步程式設計函式PromiseAI
- 理解非同步之美:Promise 與 async await(二)非同步PromiseAI
- 理解非同步之美:Promise與async await(一)非同步PromiseAI
- JavaScript基礎——深入學習async/awaitJavaScriptAI
- 從生成器到async/awaitAI
- 理解 async/awaitAI
- 20分鐘帶你掌握JavaScript Promise和 Async/AwaitJavaScriptPromiseAI
- JavaScript 的 Async/Await 完勝 Promise 的六個理由JavaScriptAIPromise
- Async/Await 代替 Promise.all()AIPromise
- Promise與async/await與GeneratorPromiseAI
- Promise和async await詳解PromiseAI
- Promise/async/await 研究筆記PromiseAI筆記
- 深入理解Javascript之PromiseJavaScriptPromise
- [譯] JavaScript 非同步演進史,從 Callbacks, Promises 到 Async/AwaitJavaScript非同步PromiseAI
- async/await 和 promise/promise.all 的示例AIPromise
- 單例模式,promise與async/await單例模式PromiseAI
- JavaScript Promises, async/awaitJavaScriptPromiseAI
- JavaScript async await 使用JavaScriptAI
- [完結篇] - 理解非同步之美 --- promise與async await (三)非同步PromiseAI
- JS非同步程式設計——深入理解async/awaitJS非同步程式設計AI
- 理解 js的 async/awaitJSAI
- 理解Task和和async awaitAI
- Async/Await替代Promise的6個理由AIPromise