文章目錄
- 異步函數(用 async 聲明的函數)
- 異步函數的返回值
- async/await 的使用
- 異步函數的異常處理
- 總結
感謝鐵子閱讀,覺得有幫助的話點點關注點點贊,謝謝!
異步函數(用 async 聲明的函數)
異步函數的定義
使用async關鍵字聲明的函數,稱之為異步函數。在普通函數前面加上 async 關鍵字,就成了異步函數。語法舉例:
// 寫法1:函數聲明的寫法
async function foo1() {
}// 寫法2:表達式寫法(ES5語法)
const foo2 = async function () {
}// 寫法3:表達式寫法(ES6箭頭函數語法)
const foo3 = async () => {
}// 寫法4:定義一個類,在類中添加一個異步函數
class Person {async foo4() {}
}
JS中的“異步函數”是一個專有名詞,特指用async關鍵字聲明的函數,其他函數則稱之為普通函數。如果你在一個普通函數中定義了一個異步任務,那并不叫異步函數,而是叫包含異步任務的普通函數。
async (異步的)這個單詞是 asynchronous 的縮寫;相反,sync(同步的)這單詞是 synchronous 的縮寫。
上面的異步函數代碼,執行順序與普通函數相同,默認情況下會同步執行。如果想要發揮異步執行的作用,則需要配合 await 關鍵字使用。稍后我們再講 async/await的語法。
異步函數的返回值
異步函數的返回值和普通函數差別比較大,需要特別關注。
普通函數的返回值,默認是 undefined;也可以手動 return 一個返回值,那就以手動 return的值為準。
異步函數的返回值永遠是 Promise 對象。至于這個 Promise 后續會進入什么狀態,那就要看情況了。主要有以下幾種情況:
情況1:如果異步函數內部返回的是普通值(包括 return undefined時)或者普通對象,那么Promise 的狀態為fulfilled。這個值會作為then()回調的參數。
情況2:如果異步函數內部返回的是另外一個新的 Promise,那么 Promise 的狀態將交給新的 Promise 決定。
情況3:如果異步函數內部返回的是一個對象,并且這個對象里有實現then()方法(這種對象稱為 thenable 對象),那就會執行該then()方法,并且根據then()方法的結果來決定Promise的狀態。
另外還有一種特殊情況:
情況4:如果異步函數內部在執行時遇到異常或者手動拋出異常時,那么, Promise 處于rejected 狀態。
上面這四種情況似曾相識,我們在前面學習“resolve() 傳入的參數”、“then()方法的返回值”知識點時,都有類似的情況,知識都是相通的。
默認返回值
代碼舉例:
async function foo2() {// 相當于 return undefined,也相當于 return Promise.resolve(undefined)
};async function foo3() {Promise.resolve('qianguyihao');// 相當于 return undefined,也相當于 return Promise.resolve(undefined)
};// foo2()、foo3()都是一個Promise對象
foo2().then(res => {console.log(res); // 打印結果:undefined
})foo3().then(res => {console.log(res); // 打印結果:undefined
})
代碼解釋:異步函數即便沒有手動寫返回值,也相當于 return Promise.resolve(undefined)。
返回普通值
比如下面這段代碼:
async function foo() {return 'qianguyihao'
};
可以看到,foo() 的返回值是Promise對象,不是字符串。上面的代碼等價于下面這段代碼:
async function foo() {return Promise.resolve('qianguyihao');
};
進而,我們可以通過 Promise 對象的then()方法。代碼舉例如下。
舉例1:(異步函數中手動 return 一個值)
async function foo() {return 'qianguyihao';// 上面這行代碼相當于:return Promise.resolve('qianguyihao');
};// foo() 是一個Promise對象
foo().then(res => {console.log(res); // 打印結果:qianguyihao
})
async/await 的使用
異步函數配合 await 關鍵字使用
我們可以在async聲明的異步函數中,使用 await關鍵字來暫停函數的執行,等待一個異步操作完成。溫馨提示:await 關鍵字不能在普通函數中使用,只能在異步函數中使用。
在等待異步操作期間,異步函數會暫停執行,并讓出線程,使其他代碼可以繼續執行。一旦異步操作完成,該異步函數會恢復執行,并返回一個 Promise 對象。具體解釋如下:
(1)await的后面是一個表達式,這個表達式要求是一個 Promise 對象(通常是一個封裝了異步任務的Promise對象)。await執行完成后可以得到異步結果。
(2)await 會等到當前 Promise 的狀態變為 fulfilled之后,才繼續往下執行異步函數。
async 的返回值是 Promise 對象。
本質是語法糖
async/await 是在 ES8(即ES 2017)中引入的新語法,是另外一種異步編程解決方案。
async/await 本質是 生成器 Generator 的語法糖,是對Generator的封裝。什么是語法糖呢?語法糖就是讓語法變得更加簡潔、更加舒服,有一種甜甜的感覺。
async/await 的寫法使得編寫異步代碼更加直觀和易于管理,避免了使用回調函數或Promise鏈的復雜性。認識到這一點,非常重要。
Promise、Generator、async/await的對比
我們在使用 Promise、async/await、Generator 的時候,返回的都是 Promise 的實例。
如果直接使用 Promise,則需要通過 then 來進行鏈式調用;如果使用 async/await、Generator,寫起來更像同步的代碼。
接下來,我們看看 async/await 的代碼是怎么寫的。
async/await 的基本用法
async 后面可以跟一個 Promise 實例對象。代碼舉例如下:
const request1 = function () {const promise = new Promise((resolve, reject) => {requestAjax('https://www.baidu.com/xxx_url', (res) => {if (res.retCode == 200) {// 這里的 res 是接口1的返回結果resolve('request1 success' + res);} else {reject('接口請求失敗');}});});return promise;
};async function requestData() {// 關鍵代碼const res = await request1();return res;
}
requestData().then(res => {console.log(res);
});
用 async/await 封裝Promise鏈式調用【重要】
假設現在有三個網絡請求,請求2必須依賴請求1的結果,請求3必須依賴請求2的結果,如果按照ES5的寫法,會有三層回調,會陷入“回調地獄”。
這種場景其實就是接口的多層嵌套調用。之前學過 Promise,它可以把原本的多層嵌套調用改進為鏈式調用。
而本文要學習的 async/await ,可以把原本的“多層嵌套調用”改成類似于同步的寫法,非常優雅。
代碼舉例:
// 【公共方法層】封裝 ajax 請求的偽代碼。傳入請求地址、請求參數,以及回調函數 success 和 fail。
function requestAjax(url, params, success, fail) {var xhr = new xhrRequest();// 設置請求方法、請求地址。請求地址的格式一般是:'https://api.example.com/data?' + 'key1=value1&key2=value2'xhr.open('GET', url);// 設置請求頭(如果需要)xhr.setRequestHeader('Content-Type', 'application/json');xhr.send();xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {success && success(xhr.responseText);} else {fail && fail(new Error('接口請求失敗'));}};
}// 【model層】將接口請求封裝為 Promise
function requestData1(params_1) {return new Promise((resolve, reject) => {requestAjax('https://api.qianguyihao.com/url_1', params_1, res => {// 這里的 res 是接口返回的數據。返回碼 retCode 為 0 代表接口請求成功。if (res.retCode == 0) {// 接口請求成功時調用resolve('request success' + res);} else {// 接口請求異常時調用reject({ retCode: -1, msg: 'network error' });}});});
}// requestData2、requestData3的寫法與 requestData1類似。他們的請求地址、請求參數、接口返回結果不同,所以需要挨個單獨封裝 Promise。
function requestData2(params_2) {return new Promise((resolve, reject) => {requestAjax('https://api.qianguyihao.com/url_2', params_2, res => {if (res.retCode == 0) {resolve('request success' + res);} else {reject({ retCode: -1, msg: 'network error' });}});});
}function requestData3(params_3) {return new Promise((resolve, reject) => {requestAjax('https://api.qianguyihao.com/url_3', params_3, res => {if (res.retCode == 0) {resolve('request success' + res);} else {reject({ retCode: -1, msg: 'network error' });}});});
}// 封裝:用 async ... await 調用 Promise 鏈式請求
async function getData() {// 【關鍵代碼】const res1 = await requestData1(params_1);const res2 = await requestData2(res1);const res3 = await requestData3(res2);
}getData();
上面這段代碼比較長,我們在上一章學習《Promise的鏈式調用》時,已經詳細講過了。
await 后面也可以跟一個異步函數
前面講到,await后面通常是一個執行異步任務的Promise對象。由于異步函數的返回值本身就是一個Promise,所以,我們也可以在await 后面也可以跟一個異步函數。
代碼舉例:
const request1 = function () {return new Promise((resolve, reject) => {resolve('request1 請求成功');});
};async function request2() {const res = await request1();return res;
}async function request3() {// 【關鍵代碼】request2() 既是一個異步函數,同樣也是一個 Promise,所以可以跟在 await 的后面const res = await request2();console.log('res:', res);
}request3();
異步函數的異常處理
前面講過,如果異步函數內部在執行時遇到異常或者手動拋出異常時,那么, 這個異步函數返回的Promise 處于rejected 狀態。
捕獲并處理異步函數的異常時,有兩種方式:
方式1:通過 Promise的catch()方法捕獲異常。
方式2:通過 try catch捕獲異常。
在處理異步函數的異常情況時,方式2更為常見。
如果我們不捕獲異常,則會往上層層傳遞,最終傳遞給瀏覽器,瀏覽器會在控制臺報錯。
方式1:過 Promise的catch()方法捕獲異常
function requestData1() {return new Promise((resolve, reject) => {reject('任務1失敗');})
}function requestData2() {return new Promise((resolve, reject) => {resolve('任務2成功');})
}async function getData() {// requestData1 在執行時,遇到異常await requestData1();/*由于上面的代碼在執行是遇到異常,所以,這里雖然什么都沒寫,底層默認寫了如下代碼:return Promise.reject('任務1失敗');*/// 下面這行代碼不會再走了await requestData2();
}// getData() 這個異步函數的返回值是一個 Promise,狀態為 rejected,所以會走到 catch()
getData().then(res => {console.log(res);
}).catch(err => {console.log('err:', err);
});
打印結果:
err: 任務1失敗
方式2:通過 try catch 捕獲異常
如果你覺得上面的寫法比較麻煩,還可以通過 try catch 捕獲異常。
代碼舉例:
function requestData1() {return new Promise((resolve, reject) => {reject('任務1失敗');})
}function requestData2() {return new Promise((resolve, reject) => {resolve('任務2成功');})
}async function getData() {try {// requestData1 在執行時,遇到異常await requestData1();/*由于上面的代碼在執行是遇到異常,當前任務立即終止,所以,這里雖然什么都沒寫,底層默認寫了如下代碼:return Promise.reject('任務1失敗');*/// 下面這兩代碼不會再走了console.log('qianguyihao1');await requestData2();}catch (err) {// 捕獲異常代碼console.log('err:', err);}
}getData();
console.log('qianguyihao2');
打印結果:
qianguyihao2
err1: 任務1失敗
總結
在 async 函數中,不是所有的 異步任務都需要 await。如果兩個任務在業務上沒有依賴關系,則不需要 await;也就是說,可以并發執行,不需要線性執行,避免無用的等待。
感謝鐵子閱讀,覺得有幫助的話點點關注點點贊,謝謝!