详解JavaScript Promise和Async/Await

概述

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

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

四个示例

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

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

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

首先,我们先来看看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后。

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

现在,因为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:一个猜数字的游戏

基本需求:

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

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

const enterNumber = () => {
  return new Promise((resolve, reject) => {
    // 从这开始编码
  });
};

我们要做的第一件事是向用户索要一个数字,并在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的随机数
  });
};

当用户输入一个不是数字的值。这种情况下,我们调用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的随机数

    if (isNaN(userNumber)) {
      reject(new Error("Wrong Input Type")); // 当用户输入的值非数字,抛出异常并调用reject函数
    }
  });
};

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

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

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);
    }
  });
};

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

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

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关键字,我们创建了一个异步函数,在函数内的使用方法较之前有如下不同:

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

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

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 函数

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

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

一般当从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();

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

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

下面的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

以上就是详解JavaScript Promise和Async/Await的详细内容,更多关于JavaScript Promise和Async/Await的资料请关注我们其它相关文章!

(0)

相关推荐

  • 深入理解JS异步编程-Promise

    前言 "JS 是基于单线程事件循环"的概念构建的,回调函数不会立即执行,由事件轮询去检测事件是否执行完毕,当执行完有结果后,将结果放入回调函数的参数中,然后将回调函数添加到事件队列中等待被执行. 同时也讲了回调函数的问题: 一是"回调地狱",因为异步回调函数的特点:回调函数是作为异步函数的参数,一层一层嵌套,当嵌套过多,将使代码逻辑变得混乱,也无法做好错误捕捉和处理(只能在回调函数内部 try catch). 二是回调的执行方式不符合自然语言的线性思维方式,不容易被

  • node.js Promise对象的使用方法实例分析

    本文实例讲述了node.js Promise对象的使用方法.分享给大家供大家参考,具体如下: Promise对象是干嘛用的? 将异步操作以同步操作的流程表达出来 一.Promise对象的定义 let flag = true; const hello = new Promise(function (resolve, reject) { if (false) {//异步操作成功 resolve("success"); } else { reject("error");

  • 关于javascript中的promise的用法和注意事项(推荐)

    一.promise描述 promise是javascript中标准的内置对象,用于表示一个异步操作的最终状态(是失败还是成功完成)及其结果值.它让你能够把异步操作最终成功或者失败的原因和响应的处理程序相关联,也就是说通过promise你可以自定义异步操作结束后该做什么.这样的话异步方法就和同步方法很类似,也有返回值,只不过这个返回值不是立即返回最终的值,而是返回一个promise,当promise的状态发生改变时,就会触发相应的处理程序. 一个promise无论什么时候都必然处于以下几种状态:1

  • 如何将Node.js中的回调转换为Promise

    前言 在几年前,回调是 JavaScript 中实现执行异步代码的唯一方法.回调本身几乎没有什么问题,最值得注意的是"回调地狱". 在 ES6 中引入了 Promise 作为这些问题的解决方案.最后通过引入   async/await 关键字来提供更好的体验并提高了可读性. 即使有了新的方法,但是仍然有许多使用回调的原生模块和库.在本文中,我们将讨论如何将 JavaScript 回调转换为 Promise.ES6 的知识将会派上用场,因为我们将会使用 展开操作符之类的功能来简化要做的事

  • JavaScript async/await原理及实例解析

    随着Node 7的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await. 异步编程的最高境界,就是根本不用关心它是不是异步. async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案. async 和 await 起了什么作用 async 起什么作用 这个问题的关键在于,async 函数是怎么处理它的返回值的! 我们当然希望它能直接通过return语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了.所以,写段代码来试试,看它到底会返回

  • vue中利用Promise封装jsonp并调取数据

    Promise就是一个给一步操作提供的容器,在这个容器里,有两个阶段无法改变的阶段,第一个阶段就是Pending(进行),第二个阶段就是结果阶段,包含Fulfilled(成功).Rejected(失败)两个结果. 这两个结果不会改变.然后结果结束后就会用then来执行相应的结果. new Promise((resolve,reject)=>{ 相应操作 if(异步操作成功){ resolve(value) }else{ reject(error) } }).then(value=>{ // 成

  • JS手写一个自定义Promise操作示例

    本文实例讲述了JS手写一个自定义Promise操作.分享给大家供大家参考,具体如下: 经常在面试题中会看到,让你实现一个Promsie,或者问你实现Promise的原理,所以今天就尝试利用class类的形式来实现一个Promise 为了不与原生的Promise命名冲突,这里就简单命名为MyPromise. class MyPromise { constructor(executor) { let _this = this this.state = 'pending' // 当前状态 this.v

  • Javascript异步编程async实现过程详解

    async官方DOC 介绍 node安装 npm install async --save 使用 var async = require('async') js文件 https://github.com/caolan/async/tree/master/dist async提供了很多函数用于异步流程控制,下面是async核心的几个函数,完整的函数请看async官方DOC async.map(['file1','file2','file3'], fs.stat, function(err, res

  • JS async 函数的含义和用法实例总结

    本文实例讲述了JS async 函数的含义和用法.分享给大家供大家参考,具体如下: 学习老师最后一篇文章并做总结, 前面我们认识了各种异步编程方式:回调函数,Promise对象,Generator函数, 再到两种自动执行方式:Thunk,co 好像我们为了更好的解决异步编程做了很多事情, 但是这也说明了一个问题 就是目前仍是的异步编程都有或多或少的问题,才导致我们要找对应的解决方案 今天我们仍是最后的大招:async 这个关键字在哪里见到来着? 我们可以给script标签添加 async 属性来

  • JS script脚本中async和defer区别详解

    一 引言 代码如下 <script src="https://www.google.com/recaptcha/api.js" async defer></script> 可以看到在script标签中,存在async与defer两个属性,首先这两个属性并共存,说直白点,你一个都不加,或者加两个属性其一,脚本加载规则都会不同,这点我在之前确实没仔细了解过,导致我在实际开发中遇到了这样一个问题: 我在同一个页面需要引用2个script脚本,大致如下: <scr

  • JS为什么说async/await是generator的语法糖详解

    关于async的介绍,在阮一峰的ES6入门教程中说到: async 函数是什么?一句话,它就是 Generator 函数的语法糖. 可是,为什么这么说呢? 首先,比如说有一个异步操作,使用 async/await 语法来以同步模拟异步操作. 使用 async/await 实现一个 sleep 的功能 function sleep(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(1); },

  • JS中async/await实现异步调用的方法

    async/await多个函数关联调用 async/await使得异步代码看起来像同步代码 async函数会隐式地返回一个promise,而promise的reosolve值就是函数return的值 Async/Await不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码 async声明一个异步函数 await只能在async函数中使用,后面跟一个promise对象 所以在模拟异步调用函数时,函数体内返回promise as

  • JavaScript异步编程之Promise的初步使用详解

    1. 概述 Promise对象是ES6提出的的异步编程的规范.说到异步编程,就不得不说说同步和异步这两个概念. 从字面意思理解同步编程的话,似乎指的是两个任务同步运行,如果这样理解就错了(至少笔者再没有接触到这个概念的时候有这种误解).同步和异步指的是代码指定执行的顺序(结构化编程范式的执行顺序总是由上至下,由前往后的),如果执行的顺序与代码的相同,就是同步:如果不同,就是异步. 最初,操作系统都是基于命令行的,所有的的语言设计出来也天然是同步的语句,在这种情况下,也不需要异步编程.但是很快,图

  • js利用递归与promise 按顺序请求数据的方法

    问题:项目中有一个需求,一个tabBar下面如果没有内容就不让该tabBar显示,当然至于有没有内容,需要我们通过请求的来判断,但是由于请求是异步的,如何让请求按照tabBar的顺序进行? 方案:我们可以将promise变成下一个请求,可以利用递归来实现 实施: //定义初始数据 requestlist就像tabBar列表 let requestlist = [1, 2, 3, 4, 5, 6, 7,8,9]; //每个tabBar的返回数据使用reslist装起来 let reslist =

随机推荐