一般在開發中,查詢網路API操作時往往是比較耗時的,這意味著可能需要一段時間的等待才能獲得回應。因此,為了避免程式在請求時無回應的情況,非同步程式設計就成為了開發人員的基本技能。
在JavaScript中處理非同步操作時,通常我們常常會聽到 "Promise "這個概念。但要理解它的工作原理及使用方法可能會比較抽象和難以理解。
相關推薦:《javascript影片教學》
那麼,在本文中我們將會透過實踐的方式讓你能更快速的理解它們的概念和用法,所以與許多傳統乾巴巴的教程都不同,我們將通過以下四個示例開始:
首先,我們先來看看Promise的基本形態是什麼樣的。
Promise執行時分三個狀態:pending(執行中)、fulfilled(成功)、rejected(失敗)。
new Promise(function(resolve, reject) { if (/* 异步操作成功 */) { resolve(value); //将Promise的状态由padding改为fulfilled } else { reject(error); //将Promise的状态由padding改为rejected } }) 实现时有三个原型方法then、catch、finally promise .then((result) => { //promise被接收或拒绝继续执行的情况 }) .catch((error) => { //promise被拒绝的情况 }) .finally (() => { //promise完成时,无论如何都会执行的情况 })
基本形態介紹完成了,那麼我們下面開始看看下面的範例。
使用者故事:我的朋友Kayo答應在兩週後在我的生日Party上為我做一個蛋糕。
如果一切順利且Kayo沒有生病的話,我們就會獲得一定數量的蛋糕,但如果Kayo生病了,我們就沒有蛋糕了。但不論有沒有蛋糕,我們還是會開一個生日Party。
所以對於這個範例,我們將如上的背景故事翻譯成JS程式碼,首先讓我們先建立一個傳回Promise的函數。
const onMyBirthday = (isKayoSick) => { return new Promise((resolve, reject) => { setTimeout(() => { if (!isKayoSick) { resolve(2); } else { reject(new Error("I am sad")); } }, 2000); }); };
在JavaScript中,我們可以使用new Promise()建立一個新的Promise,它接受一個參數為:(resolve,reject)=>{} 的函數。
在此函數中,resolve和reject是預設提供的回呼函數。讓我們仔細看看上面的程式碼。
當我們執行onMyBirthday函數2000ms後。
現在,因為onMyBirthday()回傳的是一個Promise,我們可以存取then、catch和finally方法。我們也可以存取早期在then和catch中使用傳遞給resolve和reject的參數。
讓我們透過以下程式碼來理解概念
如果Kayo沒有生病
onMyBirthday(false) .then((result) => { console.log(`I have ${result} cakes`); // 控制台打印“I have 2 cakes” }) .catch((error) => { console.log(error); // 不执行 }) .finally(() => { console.log("Party"); // 控制台打印“Party” });
如果Kayo生病
onMyBirthday(true) .then((result) => { console.log(`I have ${result} cakes`); // 不执行 }) .catch((error) => { console.log(error); // 控制台打印“我很难过” }) .finally(() => { console.log("Party"); // 控制台打印“Party” });
相信透過這個例子你能了解Promise的基本概念。
下面我們開始範例2
基本需求:
const enterNumber = () => { return new Promise((resolve, reject) => { // 从这开始编码 }); };
const enterNumber = () => { return new Promise((resolve, reject) => { const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字 const randomNumber = Math.floor(Math.random() * 6 + 1); // 选择一个从1到6的随机数 }); };
const enterNumber = () => { return new Promise((resolve, reject) => { const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字 const randomNumber = Math.floor(Math.random() * 6 + 1); //选择一个从1到6的随机数 if (isNaN(userNumber)) { reject(new Error("Wrong Input Type")); // 当用户输入的值非数字,抛出异常并调用reject函数 } }); };
return new Promise((resolve, reject) => { const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字 const randomNumber = Math.floor(Math.random() * 6 + 1); // 选择一个从1到6的随机数 if (isNaN(userNumber)) { reject(new Error("Wrong Input Type")); // 当用户输入的值非数字,抛出异常并调用reject函数 } if (userNumber === randomNumber) { // 如果相等,我们给用户2分 resolve({ points: 2, randomNumber, }); } else if ( userNumber === randomNumber - 1 || userNumber === randomNumber + 1 ) { // 如果userNumber与randomNumber相差1,那么我们给用户1分 resolve({ points: 1, randomNumber, }); } else { // 否则用户得0分 resolve({ points: 0, randomNumber, }); } });
const continueGame = () => { return new Promise((resolve) => { if (window.confirm("Do you want to continue?")) { // 向用户询问是否要继续游戏 resolve(true); } else { resolve(false); } }); };
const handleGuess = () => { enterNumber() // 返回一个Promise对象 .then((result) => { alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); // 当resolve运行时,我们得到用户得分和随机数 // 向用户询问是否要继续游戏 continueGame().then((result) => { if (result) { handleGuess(); // If yes, 游戏继续 } else { alert("Game ends"); // If no, 弹出游戏结束框 } }); }) .catch((error) => alert(error)); }; handleGuess(); // 执行handleGuess 函数
在这当我们调用handleGuess函数时,enterNumber()返回一个Promise对象。
如果Promise状态为resolved,我们就调用then方法,向用户告知竞猜结果与得分,并向用户询问是否要继续游戏。
如果Promise状态为rejected,我们将显示一条用户输入错误的信息。
不过,这样的代码虽然能解决问题,但读起来还是有点困难。让我们后面将使用async/await 对hanldeGuess进行重构。
网上对于 async/await 的解释已经很多了,在这我想用一个简单概括的说法来解释:async/await就是可以把复杂难懂的异步代码变成类同步语法的语法糖。
下面开始看重构后代码吧:
const handleGuess = async () => { try { const result = await enterNumber(); // 代替then方法,我们只需将await放在promise前,就可以直接获得结果 alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); const isContinuing = await continueGame(); if (isContinuing) { handleGuess(); } else { alert("Game ends"); } } catch (error) { // catch 方法可以由try, catch函数来替代 alert(error); } };
通过在函数前使用async关键字,我们创建了一个异步函数,在函数内的使用方法较之前有如下不同:
下面是我们重构后的完整代码,供参考:
const enterNumber = () => { return new Promise((resolve, reject) => { const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字 const randomNumber = Math.floor(Math.random() * 6 + 1); // 系统随机选取一个1-6的数字 if (isNaN(userNumber)) { reject(new Error("Wrong Input Type")); // 如果用户输入非数字抛出错误 } if (userNumber === randomNumber) { // 如果用户猜数字正确,给用户2分 resolve({ points: 2, randomNumber, }); } else if ( userNumber === randomNumber - 1 || userNumber === randomNumber + 1 ) { // 如果userNumber与randomNumber相差1,那么我们给用户1分 resolve({ points: 1, randomNumber, }); } else { // 不正确,得0分 resolve({ points: 0, randomNumber, }); } }); }; const continueGame = () => { return new Promise((resolve) => { if (window.confirm("Do you want to continue?")) { // 向用户询问是否要继续游戏 resolve(true); } else { resolve(false); } }); }; const handleGuess = async () => { try { const result = await enterNumber(); // await替代了then函数 alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); const isContinuing = await continueGame(); if (isContinuing) { handleGuess(); } else { alert("Game ends"); } } catch (error) { // catch 方法可以由try, catch函数来替代 alert(error); } }; handleGuess(); // 执行handleGuess 函数
我们已经完成了第二个示例,接下来让我们开始看看第三个示例。
一般当从API中获取数据时,开发人员会精彩使用Promises。如果在新窗口打开https://restcountries.eu/rest/v2/alpha/cn,你会看到JSON格式的国家数据。
通过使用Fetch API,我们可以很轻松的获得数据,以下是代码:
const fetchData = async () => { const res = await fetch("https://restcountries.eu/rest/v2/alpha/cn"); // fetch() returns a promise, so we need to wait for it const country = await res.json(); // res is now only an HTTP response, so we need to call res.json() console.log(country); // China's data will be logged to the dev console }; fetchData();
现在我们获得了所需的国家/地区数据,让我们转到最后一项任务。
下面的fetchCountry函数从示例3中的api获得国家信息,其中的参数alpha3Code 是代指该国家的国家代码,以下是代码
// Task 4: 获得中国周边的邻国信息 const fetchCountry = async (alpha3Code) => { try { const res = await fetch( `https://restcountries.eu/rest/v2/alpha/${alpha3Code}` ); const data = await res.json(); return data; } catch (error) { console.log(error); } };
下面让我们创建一个fetchCountryAndNeighbors函数,通过传递cn作为alpha3code来获取中国的信息。
const fetchCountryAndNeighbors = async () => { const china= await fetchCountry("cn"); console.log(china); }; fetchCountryAndNeighbors();
在控制台中,我们看看对象内容:
在对象中,有一个border属性,它是中国周边邻国的alpha3codes列表。
现在,如果我们尝试通过以下方式获取邻国信息。
const neighbors = china.borders.map((border) => fetchCountry(border));
neighbors是一个Promise对象的数组。
当处理一个数组的Promise时,我们需要使用Promise.all。
const fetchCountryAndNeigbors = async () => { const china = await fetchCountry("cn"); const neighbors = await Promise.all( china.borders.map((border) => fetchCountry(border)) ); console.log(neighbors); }; fetchCountryAndNeigbors();
在控制台中,我们应该能够看到国家/地区对象列表。
以下是示例4的所有代码,供您参考:
const fetchCountry = async (alpha3Code) => { try { const res = await fetch( `https://restcountries.eu/rest/v2/alpha/${alpha3Code}` ); const data = await res.json(); return data; } catch (error) { console.log(error); } }; const fetchCountryAndNeigbors = async () => { const china = await fetchCountry("cn"); const neighbors = await Promise.all( china.borders.map((border) => fetchCountry(border)) ); console.log(neighbors); }; fetchCountryAndNeigbors();
完成这4个示例后,你可以看到Promise在处理异步操作或不是同时发生的事情时很有用。相信在不断的实践中,对它的理解会越深、越强,希望这篇文章能对大家理解Promise和Async/Await带来一些帮助。
以下是本文中使用的代码:
https://files.cnblogs.com/files/powertoolsteam/Promise-Async-Await-main.zip
原文出处:https://www.freecodecamp.org/news/learn-promise-async-await-in-20-minutes/
更多编程相关知识,请访问:编程视频!!
以上是快速學習JavaScript中的Promise和Async/Await的詳細內容。更多資訊請關注PHP中文網其他相關文章!