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

原文出处: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(失败)。

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

示例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

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. 多MDS变成单MDS的方法

    前言 之前有个cepher的环境上是双活MDS的,需要变成MDS,目前最新版本是支持这个操作的 方法 设置最大mds 多活的mds的max_mds会超过1,这里需要先将max_mds设置为1 ceph ...

  2. Elasticsearch 第八篇:数据类型 Array、Nested、Object 的设计与应用

    h2.post_title { background-color: rgba(43, 102, 149, 1); color: rgba(255, 255, 255, 1); font-size: 1 ...

  3. SQL SERVER 数据库自动备份及定期删除设置步骤

    现在任何的软件都离不了一个数据库,数据的利用价值越来越大,为了避免数据宕机造成的数据丢失情况的产生,定期对数据库进行备份是必须要做的工作,下面将介绍SQL Server自带的数据库备份方法,希望可以帮 ...

  4. 如何获取流式应用程序中checkpoint的最新offset

    对于流式应用程序,保证应用7*24小时的稳定运行,是非常必要的.因此对于计算引擎,要求必须能够适应与应用程序逻辑本身无关的问题(比如driver应用失败重启.网络问题.服务器问题.JVM崩溃等),具有 ...

  5. 花了三天整理,Spring Cloud微服务如何设计异常处理机制?还看不懂算我输

    前言 首先说一下为什么发这篇文章,是这样的.之前和粉丝聊天的时候有聊到在采用Spring Cloud进行微服务架构设计时,微服务之间调用时异常处理机制应该如何设计的问题.我们知道在进行微服务架构设计时 ...

  6. ABBYY FineReader 14新建任务窗口给我们哪些帮助?

    当您启动ABBYY FineReader时, 新任务 将打开一个窗口,在其中您可以轻松打开.扫描.创建或对比文档. 如果您没有看到此 内置任务 窗口(比如,如果您关闭了该窗口,或者您通过在 Windo ...

  7. FL Studio录制面板作用介绍

    在上一节教程中我们详细的讲解了一下FL Studio录制面板菜单的一些功能,今天我们将继续讲解该面板的知识.具体内容小编这里就不多说了,还是一起来看看吧! 1.录音倒数.该按钮在打开的情况下会在录音前 ...

  8. 思维导图软件iMindMap怎么用模板制作思维导图

    随着思维导图的不断发展,市场上相关的软件也越来越多.像XMind.MindManager等.每一款软件都有它独特的亮点.作为众多思维导图软件中的一款,iMindMap算是比较亮眼的了.现在很多人都在用 ...

  9. 浅谈 STL

    简介 STL是Standard Template Library的简称,中文名标准模板库,从根本上说,STL是一些"容器"的集合,这些"容器"有list,vec ...

  10. C语言讲义——全局变量和局部变量

    局部变量 普通的局部变量也叫动态变量,默认有个关键字叫auto,可以省略.有两种形式: 1.函数内的局部变量 2.复合语句内的局部变量:for(int i = 0; i<5; i++){-} 静 ...