大家好,前幾篇文章我們一起學習了「JAVAScript基礎」Promise使用指南, 明白了ES6增加的新特性——Promise讓我們能夠更加優雅的書寫回調函數,清楚了Promise有哪些狀態,以及如何編寫Promise的相關代碼。本篇文章,小編將和大家一起學習異步編程的未來——async/await,它會打破你對上篇文章Promise的認知,竟然異步代碼還能這么寫! 但是別太得意,你需要深入理解Promise后,才能更好的的駕馭async/await,因為async/await是基于Promise的,沒有理解Promise,小編強烈建議各位再看看「JavaScript基礎」Promise使用指南。如果你一旦掌握了如何使用async/await,就沒必要使用Promise了(除非你需要將回調類型的API轉換為async/await,你需要使用到Promise)。
關于async / await
- 用于編寫異步程序
- 代碼書寫方式和同步編碼十分相似,因此代碼十分簡潔易讀
- 基于Promise
- 您可以使用try和catch常規的方法捕獲異常
- ES8中引入了async/await,目前幾乎所有瀏覽器都已支持這個特性(除了IE和Opera不支持)
- 你可以輕松設置斷點,調試更容易。
從async開始學起
讓我們從async關鍵字開始吧,這個關鍵詞可以放在函數之前,如下所示:
async function f() {
return 1;
}
在函數之間加上async意味著:函數將返回一個Promise,雖然你的代碼里沒有顯示的生命返回一個Promise,但是編譯器會自動將其轉換成一個Promise中,不信你可以使用Promise的then語法試試:
async function f() {
return 1;
}
f().then(alert); // 1
…如果你不放心的話,你可以再代碼里明確返回一個Promise,輸出結果是相同的。
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
很簡單吧,小編之所以說 async/await 是基于Promise是沒毛病的,async確保函數返回一個Promise,很簡單吧,不僅如此,還有一個關鍵字await,await只能在async中運行。
等待——await
await的基本語法:
let value=await promise;
該關鍵字的await的意思就是讓JS編譯器等待Promise并返回結果。接下來我們看一段簡單的示例:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait till the promise resolves (*)
alert(result); // "done!"
}
f();
函數執行將會在 let result = await promise 這一行暫停,直到Promise返回結果,因此上述代碼將會1秒后,在瀏覽器彈出“done”的提示框。
小編在此強調下:
- await的字面意思就是讓JavaScript等到Promise結束,然后輸出結果。這里并不會占用CPU資源,因為引擎可以同時執行其他任務:其他腳本或處理事件。
- 不能單獨使用await,必須在async函數作用域下使用,否則將會報出異常“Error: await is only valid in async function”,比如以下代碼:
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}
接下來,小編將和大家一起來親自動手實踐以下內容:
- async與Promise.then的結合使用,依次處理多個執行結果
- 使用await替代Promise.then,依次處理多個執行結果
- 同時等待多個執行結果
- 使用Promise.all收集多個結果
- 使用try-catch捕獲異常
- 如何處理Promise.all中拋出的錯誤
- 使用finally確保函數執行
一起動手之前,確保你安裝了Node,NPM相關工具,谷歌瀏覽器,為了預覽代碼效果,小編使用 npm install http-server -g 命令快速部署了web服務環境,方便我們運行代碼。接下來,我們寫一個火箭發射場景的小例子。
async與Promise.then的結合使用,依次處理多個執行結果
- 通過控制臺命令切換至工作區
- 創建一個async-function-Promise-chain的文件夾
- 在main.js中用創建第一個返回隨機函數的async函數getRandomNumber:
async function getRandomNumber() {
console.log('Getting random number.');
return Math.random();
}
- 在創建一個async函數determinReadyToLaunch:如果傳入參數大于0.5將返回True
async function deteremineReadyToLaunch(percentage) {
console.log('Determining Ready to launch.');
return percentage>0.5;
}
- 創建第三個async函數reportResults,如果傳入參數為True將進入倒計時發射
async function reportResults(isReadyToLaunch) {
if (isReadyToLaunch) {
console.log('Rocket ready to launch. Initiate countdown: ');
} else {
console.error('Rocket not ready. Abort mission: ');
}
}
- 創建一個main函數,調用getRandomNumber函數,并且通過Promise.then方法相機調用determineReadyToLaunch和reportResults函數
export function main() {
console.log('Before Promise created');
getRandomNumber()
.then(deteremineReadyToLaunch)
.then(reportResults)
console.log('After Promise created');
}
- 新建一個html文件引入main.js
<html>
<script type="module">
import {main} from './main10.js';
main();
</script>
<body>
</body>
</html>
- 在工作區域運行 http-server 命令,你將會看到如下輸出
使用await替代Promise.then,依次處理多個執行結果
上一節,我們使用Promise.then依次處理了多個執行結果,本小節,小編將使用await實現同樣的功能,具體操作如下:
- 通過控制臺命令切換至工作區
- 創建一個async-function-Promise-chain的文件夾
- 在main.js中用創建第一個返回隨機函數的async函數getRandomNumber:
async function getRandomNumber() {
console.log('Getting random number.');
return Math.random();
}
- 在創建一個async函數determinReadyToLaunch:如果傳入參數大于0.5將返回True
async function deteremineReadyToLaunch(percentage) {
console.log('Determining Ready to launch.');
return percentage>0.5;
}
- 創建第三個async函數reportResults,如果傳入參數為True將進入倒計時發射
async function reportResults(isReadyToLaunch) {
if (isReadyToLaunch) {
console.log('Rocket ready to launch. Initiate countdown: ');
} else {
console.error('Rocket not ready. Abort mission: ');
}
}
- 創建一個main函數,調用getRandomNumber函數,并且通過Promise.then方法相機調用determineReadyToLaunch和reportResults函數
export async function main() {
const randomNumber = await getRandomNumber();
const ready = await deteremineReadyToLaunch(randomNumber);
await reportResults(ready);
}
- 在工作區域運行 http-server 命令,你將會看到如下輸出
同時等待多個執行結果
有時候我們需要同時啟動多個異步,無需依次等待結果消耗時間,接下來的例子可以使用await 同時啟動和等待多個結果。
- 通過控制臺命令切換至工作區
- 創建一個await-concurrently的文件夾
- 創建三個函數功能checkEngines,checkFlightPlan,和checkNavigationSystem用來記錄信息時,這三個函數都返回一個Promise,示例代碼如下:
function checkEngines() {
console.log('checking engine');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('engine check completed');
resolve(Math.random() < 0.9)
}, 250)
});
}
function checkFlightPlan() {
console.log('checking flight plan');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('flight plan check completed');
resolve(Math.random() < 0.9)
}, 350)
});
}
function checkNavigationSystem() {
console.log('checking navigation system');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('navigation system check completed');
resolve(Math.random() < 0.9)
}, 450)
});
}
- 創建一個async 的main函數調用上一步創建函數。將每個返回的值分配給局部變量。然后等待Promise的結果,并輸出結果:
export async function main() {
const enginePromise = checkEngines();
const flighPlanPromise = checkFlightPlan();
const navSystemPromise = checkNavigationSystem();
const enginesOk = await enginePromise;
const flighPlanOk = await flighPlanPromise;
const navigationOk = await navSystemPromise;
if (enginesOk && flighPlanOk && navigationOk) {
console.log('All systems go, ready to launch: ');
} else {
console.error('Abort the launch: ');
if (!enginesOk) {
console.error('engines not ready');
}
if (flighPlanOk) {
console.error('error found in flight plan');
}
if (navigationOk) {
console.error('error found in navigation systems');
}
}
}
- 在工作區域運行 http-server 命令,你將會看到如下輸出
使用Promise.all收集多個結果
在上一小節中,我們一起學習了如何觸發多個異步并等待多個異步結果。上一節我們只使用了asyc/ await,本節小編和大家一起使用Promise.all來收集多個異步的結果,在某些情況下,盡量使用Promise相關的API,具體的代碼如下:
- 通過控制臺命令切換至工作區
- 創建一個Promise-all-collect-concurrently的文件夾
創建三個函數功能checkEngines,checkFlightPlan,和checkNavigationSystem用來記錄信息時,這三個函數都返回一個Promise,示例代碼如下:
function checkEngines() {
console.log('checking engine');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('engine check completed');
resolve(Math.random() < 0.9)
}, 250)
});
}
function checkFlightPlan() {
console.log('checking flight plan');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('flight plan check completed');
resolve(Math.random() < 0.9)
}, 350)
});
}
function checkNavigationSystem() {
console.log('checking navigation system');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('navigation system check completed');
resolve(Math.random() < 0.9)
}, 450)
});
}
- 創建一個async 的main函數調用上一步創建函數。使用Promise.all收集多個結果,將結果返回給變量,代碼實現如下:
export async function main() {
const prelaunchChecks = [
checkEngines(),
checkFlightPlan(),
checkNavigationSystem()
];
const checkResults = await Promise.all(prelaunchChecks);
const readyToLaunch = checkResults.reduce((acc, curr) => acc &&
curr);
if (readyToLaunch) {
console.log('All systems go, ready to launch: ');
} else {
console.error('Something went wrong, abort the launch: ');
} }
}
- 在工作區域運行 http-server 命令,你將會看到如下輸出
Promise.all接收多個promise的數組,并整體返回一個Promise,如果和上一小節的代碼進行比較,代碼量少了不少,但是也有個問題,不能返回是哪一步失敗。
使用try-catch捕獲異常
并非所有的async都能成功返回,我們需要能夠處理程序的異常,在本小節中,你將會看到如何使用try-catch捕獲async函數引發的錯誤,具體操作的流程如下:
- 通過控制臺命令切換至工作區
- 創建一個async-errors-try-catch的文件夾
- 創建一個拋出錯誤的async函數addBoosters
async function addBoosters() {
throw new Error('Unable to add Boosters');
}
- 創建一個async函數,performGuidanceDiagnostic它也會拋出一個錯誤:
async function performGuidanceDiagnostic (rocket) {
throw new Error('Unable to finish guidance diagnostic'));
}
- 創建一個async的main函數調用函數addBosters與performGuidanceDiagnostic ,使用try-catch處理錯誤:
export async function main() {
console.log('Before Check');
try {
await addBosters();
await performGuidanceDiagnostic();
} catch (e) {
console.error(e);
}
}
console.log('After Check');
- 在工作區域運行 http-server 命令,你將會看到如下輸出
從輸出看出,我們使用我們熟悉的try-catch捕獲到了異常,如果第一個發生異常,第二個就不會執行,同時將會記錄到發生的異常,并輸出到控制臺,在下一小節,我們一起將學習到如何使用try-catch捕獲同時運行多個異步操作的異常。
如何處理Promise.all中拋出的錯誤
在上面的小節中,我們使用了Promise.all來收集多個異步的執行結果。在收集錯誤狀態,Promise.all更有趣。通常,我們在處理多個錯誤時,同時顯示多個錯誤信息,我們必須編寫相關的業務邏輯。但是,在這小節,你將會使用Promise.all和try-catch捕獲異常,無需編寫復雜的布爾邏輯處理業務,具體如何實現示例如下:
- 通過控制臺命令切換至工作區
- 創建一個Promise-all-collect-concurrently的文件夾
- 創建三個async功能checkEngines,checkFlightPlan以及checkNavigationSystem函數用來記錄信息時,返回Promise,一個成功的值的信息和一個失敗值的信息:
function checkEngines() {
console.log('checking engine');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('engine check completed');
resolve(Math.random() < 0.9)
}, 250)
});
}
function checkFlightPlan() {
console.log('checking flight plan');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('flight plan check completed');
resolve(Math.random() < 0.9)
}, 350)
});
}
function checkNavigationSystem() {
console.log('checking navigation system');
return new Promise(function (resolve) {
setTimeout(function() {
console.log('navigation system check completed');
resolve(Math.random() < 0.9)
}, 450)
});
}
創建一個async的main函數調用每個在上一步中創建的功能函數。等待結果,捕獲并記錄引發的任何錯誤。如果沒有拋出錯誤,則記錄成功:
export async function main() {
try {
const prelaunchChecks = [
checkEngines,
checkFlightPlan,
checkNavigationSystem
];
await Promise.all(prelauchCheck.map((check) => check());
console.log('All systems go, ready to launch: ');
} catch (e) {
console.error('Aborting launch: ');
console.error(e);
}
}
}
- 在工作區域運行 http-server 命令,你將會看到如下輸出
Promise.all返回一個Promise,當await在錯誤狀態下,會拋出異常。三個異步promise同時執行,如果其中一個或多個錯誤得到滿足,則會拋出一個或多個錯誤;
你會發現只有一個錯誤會被記錄下來,與同步代碼一樣,我們的代碼可能會拋出多個異常,但只有一會被catch塊捕獲并記錄。
使用finally確保函數執行
錯誤處理可能會變得相當復雜。有些情況,其中您希望錯誤繼續冒泡調用堆棧以便執行其它更高級別處理。在這些情況下,您可能還需要執行一些清理任務。本小節,你將了解如何使用finally以確保執行某些代碼,而不管錯誤狀態如何,具體如何實現示例如下:
- 通過控制臺命令切換至工作區
- 創建一個Promise-all-collect-concurrently的文件夾
- 創建三個async功能checkEngines,checkFlightPlan以及checkNavigationSystem函數用來記錄信息時,返回Promise,一個成功的值的信息和一個失敗值的信息:
function checkEngines() {
console.log('checking engine');
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (Math.random() > 0.5) {
reject(new Error('Engine check failed'));
} else {
console.log('Engine check completed');
resolve();
}
}, 250)
});
}
function checkFlightPlan() {
console.log('checking flight plan');
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (Math.random() > 0.5) {
reject(new Error('Flight plan check failed'));
} else {
console.log('Flight plan check completed');
resolve();
}
}, 350)
});
}
function checkNavigationSystem() {
console.log('checking navigation system');
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (Math.random() > 0.5) {
reject(new Error('Navigation system check failed'));
} else {
console.log('Navigation system check completed');
resolve();
}
}, 450)
});
}
- 創建一個asyncperformCheck函數,調用上一步中創建的每個函數。等待結果,并用于finally記錄完整的消息:
async function performChecks() {
console.log('Starting Pre-Launch Checks');
try {
const prelaunchChecks = [
checkEngines,
checkFlightPlan,
checkNavigationSystem
];
return Promise.all(prelauchCheck.map((check) => check());
} finally {
console.log('Completed Pre-Launch Checks');
}
}
- 創建一個async的main函數調該函數performChecks。等待結果,捕獲并記錄引發的錯誤。
export async function main() {
try {
await performChecks();
console.log('All systems go, ready to launch: ');
} catch (e) {
console.error('Aborting launch: ');
console.error(e);
}
}
- 在工作區域運行 http-server 命令,你將會看到如下輸出
與上一小節一樣,錯誤在main函數中進行捕獲,由于finally的存在,讓我清楚的知道performChecks確保執行輸出已完成。你可以設想,處理錯誤是一個重要的任務,并且async/await允許我們使用try/catch的相同方式處理異步和同步代碼的錯誤,大大簡化了我們處理錯誤的工作量,讓代碼更加簡潔。
用async/await改寫上篇文章Promise的例子
上篇文章「JavaScript基礎」Promise使用指南的最后,我們使用Promise的方法改寫了基于回調的例子,本文的最后,我們將用今天學到的內容 async/await改寫這個例子, 如何實現呢,代碼如下:
const fs = require('fs');
const path = require('path');
const postsUrl = path.join(__dirname, 'db/posts.json');
const commentsUrl = path.join(__dirname, 'db/comments.json');
//return the data from our file
function loadCollection(url) {
return new Promise(function(resolve, reject) {
fs.readFile(url, 'utf8', function(error, data) {
if (error) {
reject('error');
} else {
resolve(JSON.parse(data));
}
});
});
}
//return an object by id
function getRecord(collection, id) {
return new Promise(function(resolve, reject) {
const data = collection.find(function(element){
return element.id == id;
});
resolve(data);
});
}
//return an array of comments for a post
function getCommentsByPost(comments, postId) {
return comments.filter(function(comment){
return comment.postId == postId;
});
}
async function getPost(){
try {
const posts = await loadCollection(postsUrl);
const post = await getRecord(posts, "001");
const comments = await loadCollection(commentsUrl);
const postComments = await getCommentsByPost(comments, post.id);
console.log(post);
console.log(postComments);
} catch (error) {
console.log(error);
}
}
getPost();
和Promise的方式相比,async/await 的實現方式是不是更直觀更容易理解呢,讓我幾乎能用同步的方式編寫異步代碼。
結束語
本節內容就介紹到這里,我們學會了如何使用 async/await 的使用,并且學會了如何與Promise相關API進行結合,async/await 讓我們以同步的方式更容易的編寫異步代碼,大大降低了編寫異步函數的難度。
更多精彩內容,請微信關注”前端達人”公眾號!






