转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

原文出处:https://www.freecodecamp.org/news/learn-promise-async-await-in-20-minutes/

一般在开发中,查询网络API操作时往往是比较耗时的,这意味着可能需要一段时间的等待才能获得响应。因此,为了避免程序在请求时无响应的情况,异步编程就成为了开发人员的一项基本技能。

在JavaScript中处理异步操作时,通常我们经常会听到 "Promise "这个概念。但要理解它的工作原理及使用方法可能会比较抽象和难以理解。

那么,在本文中我们将会通过实践的方式让你能更快速的理解它们的概念和用法,所以与许多传统干巴巴的教程都不同,我们将通过以下四个示例开始:

  • 示例1:用生日解释Promise的基础知识
  • 示例2:一个猜数字的游戏
  • 示例3:从Web API中获取国家信息
  • 示例4:从Web API中获取一个国家的周边国家列表

示例1:用生日解释Promise基础知识

首先,我们先来看看Promise的基本形态是什么样的。

Promise执行时分三个状态:pending(执行中)、fulfilled(成功)、rejected(失败)。

  1. new Promise(function(resolve, reject) {
  2. if (/* 异步操作成功 */) {
  3. resolve(value); //将Promise的状态由padding改为fulfilled
  4. } else {
  5. reject(error); //将Promise的状态由padding改为rejected
  6. }
  7. })
  8. 实现时有三个原型方法thencatchfinally
  9. promise
  10. .then((result) => {
  11. //promise被接收或拒绝继续执行的情况
  12. })
  13. .catch((error) => {
  14. //promise被拒绝的情况
  15. })
  16. .finally (() => {
  17. //promise完成时,无论如何都会执行的情况
  18. })

基本形态介绍完成了,那么我们下面开始看看下面的示例吧。

用户故事:我的朋友Kayo答应在两周后在我的生日Party上为我做一个蛋糕。

如果一切顺利且Kayo没有生病的话,我们就会获得一定数量的蛋糕,但如果Kayo生病了,我们就没有蛋糕了。但不论有没有蛋糕,我们仍然会开一个生日Party。

所以对于这个示例,我们将如上的背景故事翻译成JS代码,首先让我们先创建一个返回Promise的函数。

  1. const onMyBirthday = (isKayoSick) => {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. if (!isKayoSick) {
  5. resolve(2);
  6. } else {
  7. reject(new Error("I am sad"));
  8. }
  9. }, 2000);
  10. });
  11. };

在JavaScript中,我们可以使用new Promise()创建一个新的Promise,它接受一个参数为:(resolve,reject)=>{} 的函数。

在此函数中,resolve和reject是默认提供的回调函数。让我们仔细看看上面的代码。

当我们运行onMyBirthday函数2000ms后。

  • 如果Kayo没有生病,那么我们就以2为参数执行resolve函数
  • 如果Kayo生病了,那么我们用new Error("I am sad")作为参数执行reject。尽管您可以将任何要拒绝的内容作为参数传递,但建议将其传递给Error对象。

现在,因为onMyBirthday()返回的是一个Promise,我们可以访问then、catch和finally方法。我们还可以访问早些时候在then和catch中使用传递给resolve和reject的参数。

让我们通过如下代码来理解概念

如果Kayo没有生病

  1. onMyBirthday(false)
  2. .then((result) => {
  3. console.log(`I have ${result} cakes`); // 控制台打印“I have 2 cakes”
  4. })
  5. .catch((error) => {
  6. console.log(error); // 不执行
  7. })
  8. .finally(() => {
  9. console.log("Party"); // 控制台打印“Party”
  10. });

如果Kayo生病

  1. onMyBirthday(true)
  2. .then((result) => {
  3. console.log(`I have ${result} cakes`); // 不执行
  4. })
  5. .catch((error) => {
  6. console.log(error); // 控制台打印“我很难过”
  7. })
  8. .finally(() => {
  9. console.log("Party"); // 控制台打印“Party”
  10. });

相信通过这个例子你能了解Promise的基本概念。

下面我们开始示例2

示例2:一个猜数字的游戏

基本需求:

  • 用户可以输入任意数字
  • 系统从1到6中随机生成一个数字
  • 如果用户输入数字等于系统随机数,则给用户2分
  • 如果用户输入数字与系统随机数相差1,给用户1分,否则,给用户0分
  • 用户想玩多久就玩多久

对于上面的需求,我们首先创建一个enterNumber函数并返回一个Promise:

  1. const enterNumber = () => {
  2. return new Promise((resolve, reject) => {
  3. // 从这开始编码
  4. });
  5. };

我们要做的第一件事是向用户索要一个数字,并在1到6之间随机选择一个数字:

  1. const enterNumber = () => {
  2. return new Promise((resolve, reject) => {
  3. const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
  4. const randomNumber = Math.floor(Math.random() * 6 + 1); // 选择一个从1到6的随机数
  5. });
  6. };

当用户输入一个不是数字的值。这种情况下,我们调用reject函数,并抛出错误:

  1. const enterNumber = () => {
  2. return new Promise((resolve, reject) => {
  3. const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
  4. const randomNumber = Math.floor(Math.random() * 6 + 1); //选择一个从1到6的随机数
  5.  
  6. if (isNaN(userNumber)) {
  7. reject(new Error("Wrong Input Type")); // 当用户输入的值非数字,抛出异常并调用reject函数
  8. }
  9. });
  10. };

下面,我们需要检查userNumber是否等于RanomNumber,如果相等,我们给用户2分,然后我们可以执行resolve函数来传递一个object { points: 2, randomNumber } 对象。

如果userNumber与randomNumber相差1,那么我们给用户1分。否则,我们给用户0分。

  1. return new Promise((resolve, reject) => {
  2. const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
  3. const randomNumber = Math.floor(Math.random() * 6 + 1); // 选择一个从1到6的随机数
  4.  
  5. if (isNaN(userNumber)) {
  6. reject(new Error("Wrong Input Type")); // 当用户输入的值非数字,抛出异常并调用reject函数
  7. }
  8.  
  9. if (userNumber === randomNumber) {
  10. // 如果相等,我们给用户2分
  11. resolve({
  12. points: 2,
  13. randomNumber,
  14. });
  15. } else if (
  16. userNumber === randomNumber - 1 ||
  17. userNumber === randomNumber + 1
  18. ) {
  19. // 如果userNumber与randomNumber相差1,那么我们给用户1分
  20. resolve({
  21. points: 1,
  22. randomNumber,
  23. });
  24. } else {
  25. // 否则用户得0分
  26. resolve({
  27. points: 0,
  28. randomNumber,
  29. });
  30. }
  31. });

下面,让我们再创建一个函数来询问用户是否想继续游戏:

  1. const continueGame = () => {
  2. return new Promise((resolve) => {
  3. if (window.confirm("Do you want to continue?")) { // 向用户询问是否要继续游戏
  4. resolve(true);
  5. } else {
  6. resolve(false);
  7. }
  8. });
  9. };

为了不使游戏强制结束,我们创建的Promise没有使用Reject回调。

下面,我们创建一个函数来处理猜数字逻辑:

  1. const handleGuess = () => {
  2. enterNumber() // 返回一个Promise对象
  3. .then((result) => {
  4. alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); // 当resolve运行时,我们得到用户得分和随机数
  5.  
  6. // 向用户询问是否要继续游戏
  7. continueGame().then((result) => {
  8. if (result) {
  9. handleGuess(); // If yes, 游戏继续
  10. } else {
  11. alert("Game ends"); // If no, 弹出游戏结束框
  12. }
  13. });
  14. })
  15. .catch((error) => alert(error));
  16. };
  17.  
  18. handleGuess(); // 执行handleGuess 函数

在这当我们调用handleGuess函数时,enterNumber()返回一个Promise对象。

如果Promise状态为resolved,我们就调用then方法,向用户告知竞猜结果与得分,并向用户询问是否要继续游戏。

如果Promise状态为rejected,我们将显示一条用户输入错误的信息。

不过,这样的代码虽然能解决问题,但读起来还是有点困难。让我们后面将使用async/await 对hanldeGuess进行重构。

网上对于 async/await 的解释已经很多了,在这我想用一个简单概括的说法来解释:async/await就是可以把复杂难懂的异步代码变成类同步语法的语法糖

下面开始看重构后代码吧:

  1. const handleGuess = async () => {
  2. try {
  3. const result = await enterNumber(); // 代替then方法,我们只需将await放在promise前,就可以直接获得结果
  4.  
  5. alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);
  6.  
  7. const isContinuing = await continueGame();
  8.  
  9. if (isContinuing) {
  10. handleGuess();
  11. } else {
  12. alert("Game ends");
  13. }
  14. } catch (error) { // catch 方法可以由try, catch函数来替代
  15. alert(error);
  16. }
  17. };

通过在函数前使用async关键字,我们创建了一个异步函数,在函数内的使用方法较之前有如下不同:

  • 和then函数不同,我们只需将await关键字放在Promise前,就可以直接获得结果。
  • 我们可以使用try, catch语法来代替promise中的catch方法。

下面是我们重构后的完整代码,供参考:  

  1. const enterNumber = () => {
  2. return new Promise((resolve, reject) => {
  3. const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // 向用户索要一个数字
  4. const randomNumber = Math.floor(Math.random() * 6 + 1); // 系统随机选取一个1-6的数字
  5.  
  6. if (isNaN(userNumber)) {
  7. reject(new Error("Wrong Input Type")); // 如果用户输入非数字抛出错误
  8. }
  9.  
  10. if (userNumber === randomNumber) { // 如果用户猜数字正确,给用户2分
  11. resolve({
  12. points: 2,
  13. randomNumber,
  14. });
  15. } else if (
  16. userNumber === randomNumber - 1 ||
  17. userNumber === randomNumber + 1
  18. ) { // 如果userNumber与randomNumber相差1,那么我们给用户1分
  19. resolve({
  20. points: 1,
  21. randomNumber,
  22. });
  23. } else { // 不正确,得0分
  24. resolve({
  25. points: 0,
  26. randomNumber,
  27. });
  28. }
  29. });
  30. };
  31.  
  32. const continueGame = () => {
  33. return new Promise((resolve) => {
  34. if (window.confirm("Do you want to continue?")) { // 向用户询问是否要继续游戏
  35. resolve(true);
  36. } else {
  37. resolve(false);
  38. }
  39. });
  40. };
  41.  
  42. const handleGuess = async () => {
  43. try {
  44. const result = await enterNumber(); // await替代了then函数
  45.  
  46. alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);
  47.  
  48. const isContinuing = await continueGame();
  49.  
  50. if (isContinuing) {
  51. handleGuess();
  52. } else {
  53. alert("Game ends");
  54. }
  55. } catch (error) { // catch 方法可以由try, catch函数来替代
  56. alert(error);
  57. }
  58. };
  59.  
  60. handleGuess(); // 执行handleGuess 函数

我们已经完成了第二个示例,接下来让我们开始看看第三个示例。

示例3:从Web API中获取国家信息

一般当从API中获取数据时,开发人员会精彩使用Promises。如果在新窗口打开https://restcountries.eu/rest/v2/alpha/cn,你会看到JSON格式的国家数据。

通过使用Fetch API,我们可以很轻松的获得数据,以下是代码:

  1. const fetchData = async () => {
  2. const res = await fetch("https://restcountries.eu/rest/v2/alpha/cn"); // fetch() returns a promise, so we need to wait for it
  3.  
  4. const country = await res.json(); // res is now only an HTTP response, so we need to call res.json()
  5.  
  6. console.log(country); // China's data will be logged to the dev console
  7. };
  8.  
  9. fetchData();

现在我们获得了所需的国家/地区数据,让我们转到最后一项任务。

示例4:从Web API中获取一个国家的周边国家列表

下面的fetchCountry函数从示例3中的api获得国家信息,其中的参数alpha3Code 是代指该国家的国家代码,以下是代码

  1. // Task 4: 获得中国周边的邻国信息
  2. const fetchCountry = async (alpha3Code) => {
  3. try {
  4. const res = await fetch(
  5. `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
  6. );
  7.  
  8. const data = await res.json();
  9.  
  10. return data;
  11. } catch (error) {
  12. console.log(error);
  13. }
  14. };

下面让我们创建一个fetchCountryAndNeighbors函数,通过传递cn作为alpha3code来获取中国的信息。

  1. const fetchCountryAndNeighbors = async () => {
  2. const china= await fetchCountry("cn");
  3.  
  4. console.log(china);
  5. };
  6.  
  7. fetchCountryAndNeighbors();

在控制台中,我们看看对象内容:  

在对象中,有一个border属性,它是中国周边邻国的alpha3codes列表。

现在,如果我们尝试通过以下方式获取邻国信息。

  1. const neighbors =
  2. china.borders.map((border) => fetchCountry(border));

neighbors是一个Promise对象的数组。

当处理一个数组的Promise时,我们需要使用Promise.all。

  1. const fetchCountryAndNeigbors = async () => {
  2. const china = await fetchCountry("cn");
  3.  
  4. const neighbors = await Promise.all(
  5. china.borders.map((border) => fetchCountry(border))
  6. );
  7.  
  8. console.log(neighbors);
  9. };
  10.  
  11. fetchCountryAndNeigbors();

在控制台中,我们应该能够看到国家/地区对象列表。

以下是示例4的所有代码,供您参考:

  1. const fetchCountry = async (alpha3Code) => {
  2. try {
  3. const res = await fetch(
  4. `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
  5. );
  6. const data = await res.json();
  7. return data;
  8. } catch (error) {
  9. console.log(error);
  10. }
  11. };
  12.  
  13. const fetchCountryAndNeigbors = async () => {
  14. const china = await fetchCountry("cn");
  15. const neighbors = await Promise.all(
  16. china.borders.map((border) => fetchCountry(border))
  17. );
  18. console.log(neighbors);
  19. };
  20.  
  21. fetchCountryAndNeigbors();

  

总结

完成这4个示例后,你可以看到Promise在处理异步操作或不是同时发生的事情时很有用。相信在不断的实践中,对它的理解会越深、越强,希望这篇文章能对大家理解Promise和Async/Await带来一些帮助。

以下是本文中使用的代码:

https://files.cnblogs.com/files/powertoolsteam/Promise-Async-Await-main.zip

20分钟带你掌握JavaScript Promise和 Async/Await的更多相关文章

  1. JavaScript异步编程——Async/Await vs Promise

    兼容性 提醒一下各位,Node 现在从版本 7.6 开始就支持 async/await 了.而就在前几天,Node 8已经正式发布了,你可以放心地使用它. 如果你还没有试过它,这里有一堆带有示例的理由 ...

  2. Promise及Async/Await

      一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪 ...

  3. 异步Promise及Async/Await最完整入门攻略

    一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪圈, ...

  4. 异步Promise及Async/Await可能最完整入门攻略

    此文只介绍Async/Await与Promise基础知识与实际用到注意的问题,将通过很多代码实例进行说明,两个实例代码是setDelay和setDelaySecond. tips:本文系原创转自我的博 ...

  5. Promise和async await详解

    本文转载自Promise和async await详解 Promise 状态 pending: 初始状态, 非 fulfilled 或 rejected. fulfilled: 成功的操作. rejec ...

  6. Promise, Generator, async/await的渐进理解

    作为前端开发者的伙伴们,肯定对Promise,Generator,async/await非常熟悉不过了.Promise绝对是烂记于心,而async/await却让使大伙们感觉到爽(原来异步可以这么简单 ...

  7. callback vs async.js vs promise vs async / await

    需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = functio ...

  8. “setTimeout、Promise、Async/Await 的区别”题目解析和扩展

    解答这个题目之前,先回顾下JavaScript的事件循环(Event Loop). JavaScript的事件循环 事件循环(Event Loop):同步和异步任务分别进入不同的执行"场所& ...

  9. promise 进阶 —— async / await 结合 bluebird

    一.背景 1.Node.js 异步控制 在之前写的 callback vs async.js vs promise vs async / await 里,我介绍了 ES6 的 promise 和 ES ...

随机推荐

  1. ELF文件格式内容

    在计算机科学中,是一种用于二进制文件.可执行文件.目标代码.共享库和核心转储格式文件.   ELF文件组成部分 ELF文件由4部分组成,分别是ELF头(ELF header).程序头表(Program ...

  2. spring的原理

    一.pring的原理 1.1 IOC控制反转 ==> 扫描机制通过代理方式动态创建对象 扫描注解,通过反射获取类路径,动态创建对应类的对象,放置在对象池中(多线程做法,防止短时间内创建对象过多, ...

  3. 03、JDBC范例

    范例:JDBC查询 package com.hsp; import java.sql.Connection; import java.sql.DriverManager; import java.sq ...

  4. rinetd小工具

    一.介绍 tcp端口转发工具,针对ip:port的 连接进行转发.重点开源,且配置简单.不支持ftp,因为ftp要使用多个端口. 1.转发规则 [root@master tmp]# cat a.con ...

  5. Spring Cloud Netflix Eureka(注册中心)

    Eureka简介 Eureka是Netflix开发的一个Service Discovery组件,spring cloud将其整合用来做服务注册中心,Eureka包括两部分Eureka Server 和 ...

  6. 新同事不讲“码”德,这SQL写得太野了,请耗子尾汁~

    今天来分享几个MySQL常见的SQL错误(不当)用法.我们在作为一个初学者时,很有可能自己在写SQL时也没有注意到这些问题,导致写出来的SQL语句效率低下,所以我们也可以自省自检一下. 1. LIMI ...

  7. 使用python统计《三国演义》小说里人物出现次数前十名,并实现可视化。

    一.安装所需要的第三方库 jieba (jieba是优秀的中文分词第三分库) pyecharts (一个优秀的数据可视化库) <三国演义>.txt下载地址(提取码:kist ) 使用pyc ...

  8. C# redis集群批量操作之slot计算出16384个字符串

    引入一个大家都用的到的需求来说吧. 需求:要在三主三从的redis集群,存入数据,会对数据进行批量删除操作,数据要求要在redis集群负载均衡. 思路: 1.存入数据好办 1 var connect ...

  9. 前端(web)知识-html

    前端由三部分组成: HTML(标签)--CSS(美化,修饰)--JS(执行指令) HTML(超文本标记语言,Hypertext Markup Language):是一种用于创建网页的标记语言. 本质上 ...

  10. vulnhub: DC 3

    通过nmap扫描,只开放了80端口,并且该web服务是基于Joomla搭建: root@kali:~# nmap -A 192.168.74.140 Starting Nmap 7.80 ( http ...