您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

20分钟带你掌握JS Promise和Async/Await

时间:2021-03-31 11:05:24  来源:今日头条  作者:葡萄城GrapeCity

一般在开发中,查询网络 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的参数。

让我们通过如下代码来理解概念,相信通过这个例子你能了解Promise的基本概念。

如果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"
});

示例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();

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

20分钟带你掌握JS Promise和Async/Await

 

在对象中,有一个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();

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

20分钟带你掌握JS Promise和Async/Await

 

以下是示例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带来一些帮助。

这是本文中使用的代码:Asdkjbasrksbr。



Tags:JS Promise   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一般在开发中,查询网络 API 操作时往往是比较耗时的,这意味着可能需要一段时间的等待才能获得响应。因此,为了避免程序在请求时无响应的情况,异步编程就成为了开发人员的一项基...【详细内容】
2021-03-31  Tags: JS Promise  点击:(260)  评论:(0)  加入收藏
▌简易百科推荐
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === 'a') { b = true} else { b = false}// goodb = a === 'a'2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Mason程    Tags:JavaScript   点击:(5)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  未来讲IT    Tags:JavaScript   点击:(19)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  前端明明    Tags:js   点击:(23)  评论:(0)  加入收藏
作者:一川来源:前端万有引力 1 写在前面Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的...【详细内容】
2021-12-06  Nodejs开发    Tags:Javascript   点击:(18)  评论:(0)  加入收藏
概述DOM全称Document Object Model,即文档对象模型。是HTML和XML文档的编程接口,DOM将文档(HTML或XML)描绘成一个多节点构成的结构。使用JavaScript可以改变文档的结构、样式和...【详细内容】
2021-11-16  海人为记    Tags:DOM模型   点击:(34)  评论:(0)  加入收藏
入口函数 /*js加载完成事件*/ window.onload=function(){ console.log("页面和资源完全加载完毕"); } /*jQuery的ready函数*/ $(document).ready(function(){ co...【详细内容】
2021-11-12  codercyh的开发日记    Tags:jQuery   点击:(35)  评论:(0)  加入收藏
一、判断是否IE浏览器(支持判断IE11与edge)function IEVersion() {var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isIE = userAgent.indexOf("comp...【详细内容】
2021-11-02  V面包V    Tags:Javascript   点击:(39)  评论:(0)  加入收藏
Null、Undefined、空检查普通写法: if (username1 !== null || username1 !== undefined || username1 !== '') { let username = username1; }优化后...【详细内容】
2021-10-28  前端掘金    Tags:JavaScript   点击:(50)  评论:(0)  加入收藏
今天我们将尝试下花 1 分钟的时间简单地了解下什么是 JS 代理对象(proxies)?我们可以这样理解,JS 代理就相当于在对象的外层加了一层拦截,在拦截方法里我们可以自定义一些个性化...【详细内容】
2021-10-18  前端达人    Tags:JS   点击:(51)  评论:(0)  加入收藏
带有多个条件的 if 语句把多个值放在一个数组中,然后调用数组的 includes 方法。// bad if (x === "abc" || x === "def" || x === "ghi" || x === "jkl") { //logic } // be...【详细内容】
2021-09-27  羲和时代    Tags:JS   点击:(58)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条