异步 JavaScript 速成课程(第二部分)
介绍
承诺
异步/等待
发出 HTTP 请求
API项目
代码
index.html
app.js
结论
介绍
欢迎回来!
以下是我们第二部分将要介绍的内容。
- 承诺
- 异步和等待
- 发出 HTTP 请求
- API项目
让我们从上次中断的地方继续。首先,我们要摆脱回调地狱。
承诺
什么是承诺?
JavaScript 中的 Promise 就像现实世界中的“承诺”一词一样。当我们做出承诺时,有两种可能的结果:承诺被履行(已解决)或承诺被打破(已拒绝)。
同样,JavaScript 中的 Promise 对象有 3 种状态:
待处理:初始状态,既未解决也未拒绝。
已解决:表示操作已成功完成。
已拒绝:表示操作失败。
定义: JavaScript 中的 Promise 被定义为一个表示异步操作最终完成(或失败)的对象。
哇!那我们来仔细分析一下。
- Promise 是一个对象。
- 它常用于异步操作中。
- Promise 对象由异步函数返回,这些对象最初可能没有值,但将来某个时候会具有值。
让我们通过一个虚拟函数来理解这一点。
const fakeRequest = (url) => {
return new Promise((resolve, reject) => {
const rand = Math.random();
setTimeout(() => {
if (rand < 0.7) {
resolve('YOUR FAKE DATA HERE');
}
reject('Request Error!');
}, 1000)
})
}
这个函数本质上是在模拟远程服务器的响应。当我们向服务器请求数据时,服务器可能需要几秒钟才能完成或拒绝我们的请求。从某种意义上说,服务器是在承诺会向我们返回数据。
当函数被调用时,它会创建一个 Promise。函数可以保留(resolve)该 Promise 并返回数据,也可以拒绝(reject)该 Promise 并抛出错误。
该函数生成一个介于 0 和 1 之间的随机数。如果该数字小于 0.7,则使用 resolve 函数以数据作为参数来解决此承诺;否则,将使用 reject 函数拒绝此承诺并抛出错误。
既然我们已经创造了承诺,我们该如何使用它呢?
- 我们在函数调用中使用
.then(),.catch()来消费 promise。
。然后
- 我们传递一个回调函数,
.then该函数会在 Promise 被解析时执行。
fakeRequest("fakeurl").then((data)=>{
console.log(data)
})
- 如果 Promise 被解析,则“data”参数将包含“你的虚假数据”。
。抓住
- 如果承诺被拒绝,我们需要捕获该错误。
- 我们附加了
.catch()后置条件.then(),以捕获任何被拒绝的承诺。
fakeRequest("fakeurl").then((data)=>{
console.log(data)
}).catch((error){
console.log(error)
})
-
如果抛出任何错误,'error' 参数的值将为'请求错误!'。
-
简单来说,
.thenis 代表解决,.catchis 代表拒绝。
回调函数和 Promise 的主要区别在于,在 Promise 中,你将回调(.then 和 .catch)附加到返回的 Promise 对象,而在回调函数中,你将回调作为参数(success 和 failure)传递给函数。
你还记得我们之前用回调函数实现的彩虹函数吗?
让我们用 Promise 来重新实现它。
const delayedColorChange = (color, delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
document.body.style.backgroundColor = color;
resolve();
}, delay)
})
}
现在,如果我们想让颜色依次显示,只需.then按顺序链式调用即可。由于每个 Promise 中的代码.then只有在前一个 Promise 解析后才会执行,因此我们可以同步(依次)执行多个异步任务(例如更改背景颜色)。
类似这样的:
delayedColorChange('red', 1000)
.then(() => delayedColorChange('orange', 1000))
.then(() => delayedColorChange('yellow', 1000))
.then(() => delayedColorChange('green', 1000))
.then(() => delayedColorChange('blue', 1000))
.then(() => delayedColorChange('indigo', 1000))
.then(() => delayedColorChange('violet', 1000))
- 首先会调用 delayedColorChange 函数来更改颜色为红色。
- 当它完成并解决承诺“.then”时,将调用 delayedColorChange 来更改颜色为橙色,依此类推。
让我们来看一个真实案例。
- 有时您可能需要连续多次调用 API 或数据库来获取数据。
- 假设你需要调用两个 API 'A' 和 'B',但是要调用 'B',你需要传递从 'A' 获取的数据。
-
所以需要先解决“A”,对吧?之后
.then才能调用“B”。 -
我们可以使用我们的
fakeRequest函数来发出这些虚假的 API 请求。 -
以下是我们的做法:
fakeRequest('urlA')
.then((dataa) => {
console.log("(urlA) worked")
return fakeRequestPromise('UrlB')
})
.then((datab) => {
console.log("(urlB) worked")
})
.catch((err) => {
console.log("OH NO, A REQUEST FAILED!!!")
console.log(err)
})
- 这样做之所以有效,是因为
.then只有在 Promise 被解析后才会运行。然后我们调用 urlB API。 - 接下来,
.then附加了一个用于处理 urlB 的代码。 - 如果其中任何一个请求失败(承诺被拒绝),它们就会直接传递到
.catch. - 如果我们希望在 urlB 之后进行更多 API 调用,我们只需不断返回函数并链式调用即可
.then。
fakeRequest('urlA')
.then((dataa) => {
console.log("(urlA) worked !!!")
return fakeRequestPromise('urlB')
})
.then((datab) => {
console.log("(urlB) worked !!!")
return fakeRequestPromise('urlC')
})
.then((datac) => {
console.log("(urlC) worked !!!")
})
.catch((err) => {
console.log("OH NO, A REQUEST FAILED!!!")
console.log(err)
})
这样做的目的是为了简化代码。我们可以看到,与使用回调函数相比,使用 Promise 实现的 Rainbow 函数和模拟请求函数都显得简洁得多。
异步/等待
- Async 和 Await 是程序员所说的语法糖。
- 它本质上是一种更简便的 Promise 使用方式。
## 异步
async 关键字可以放在函数前面。
const greeting = async ()=>{
return "Nice to meet you!!"
}
- 使用 async 关键字意味着该函数现在总是返回一个 Promise。
- 无论函数返回什么类型的值,它始终会被包装在一个 Promise 中。
- 既然该函数返回的是一个 Promise,我们就可以使用以下方式调用它
.then。
greeting().then((data)=>{
console.log(data)
})
- 这将打印出“很高兴见到你!!”
- 因此,我们不需要显式地解决承诺。
## 等待
- await 关键字会暂停异步函数中的代码,直到 Promise 被解析。
- await 只能在异步函数中使用。
- 如果在异步函数之外使用 Await 关键字,则会出现语法错误。
let result = await promise;
-
让我们调用我们的 Rainbow 函数
delayedColorChange(),但这次使用 async 和 await。 -
首先,我们创建一个异步函数,该函数将调用不同颜色对应的函数。
const rainbow = async ()=>{
}
- 让我们选择
delayedColorChange红色。 - 红色部分结束后,函数应该返回“已打印”。
const printRainbow = async ()=>{
delayedColorChange('red', 1000)
return "Printed"
}
- 但是,如果我们这样执行,该函数不会等待红色显示出来,而是会立即打印“已打印”。
-
因此,我们必须要求该
printRainbow函数等待直到delayedColorChange('red', 1000)其承诺得到解决。
-
为此,我们将
await关键字放在前面delayedColorChange。 -
这将要求对方
printRainbow等待承诺得到解决。
const printRainbow = async ()=>{
await delayedColorChange('red', 1000)
return "Printed"
}
- 让我们把剩下的颜色都放进去。
const printRainbow = async ()=>{
await delayedColorChange('red', 1000)
await delayedColorChange('orange', 1000)
await delayedColorChange('yellow', 1000)
await delayedColorChange('green', 1000)
await delayedColorChange('blue', 1000)
await delayedColorChange('indigo', 1000)
await delayedColorChange('violet', 1000)
return "Printed"
}
printRainbow().then((data)=>(
console.log(data)
))
- 所有颜色显示完毕后,控制台会打印“已打印”。
为了处理错误,我们可以使用 try-catch 块。
const printRainbow = async ()=>{
try{
await delayedColorChange('red', 1000)
return "Printed"
}
catch{
console.log("error")
}
}
我们可以将其与该函数的回调版本进行比较。
//Promises (Async and Await)
const printRainbow = async ()=>{
await delayedColorChange('red', 1000)
await delayedColorChange('orange', 1000)
await delayedColorChange('yellow', 1000)
await delayedColorChange('green', 1000)
await delayedColorChange('blue', 1000)
await delayedColorChange('indigo', 1000)
await delayedColorChange('violet', 1000)
return "Printed"
}
//Callbacks
delayedColorChange('red', 1000, () => {
delayedColorChange('orange', 1000, () => {
delayedColorChange('yellow', 1000, () => {
delayedColorChange('green', 1000, () => {
delayedColorChange('blue', 1000, () => {
delayedColorChange('indigo', 1000, () => {
delayedColorChange('violet', 1000, () => {
//This function will be empty since
//we want to end the
//color change
})
})
})
})
})
})
});
我们可以看到,它更加干净整洁,结构也更加紧凑。
发出 HTTP 请求
- 现在让我们来了解异步 JavaScript 的最后一块拼图。
- 我们知道如何使用 Promise 和回调来处理来自 API 的响应。
- 现在,我们应该发出真正的 HTTP 请求,而不是使用
fakerequest()函数来伪造它。
请求头
HTTP 标头本质上是客户端和服务器之间传递附加信息的一种方式。 我们可以看到,通过标头传递的关于请求的附加信息以键值对的形式存在。
HTTP 请求的类型
- GET:用于从资源中“获取”数据。
- POST:用于向特定目标发送数据。
- PUT:用于更新现有数据。
- 删除:用于删除数据。
我们主要会使用GETrequest,因为我们想从 API 获取 JSON 数据。
在 Web API 部分,我们通过浏览器向 Pokémon API 发出的请求是一个GET请求,因为我们从 API 服务器“获取”图像数据。
但是,要在我们自己的网站中使用来自 Web API 的数据,我们必须通过 JavaScript 发出请求。
可以通过 JavaScript 以不同的方式异步向这些 Web API 发出请求。
- XMLHttpRequest
- 获取 API
- Axios
我们的网页将使用 Axios。不过,我们也先来了解一下 XHR 和 fetch API。
XMLHttpRequest
- XHR 是使用 JavaScript 发送请求的原始方式。
- 这是一个浏览器API。
- 由于这种方法使用了回调函数,并且不支持 Promise,因此并非首选方法。
基本语法如下:
var xhttp = new XMLHttpRequest();//1
xhttp.onload = function() {//4
const data = JSON.parse(this.responseText);
};
xhttp.onerror = function() { // only triggers if the request couldn't be made at all
alert(` Error`);
};
xhttp.open("GET", "filename", true);//2
xhttp.send();//3
- 我们创建一个 xhttp 对象
xhttp.open("GET", "url", true);打开请求时,需指定:请求类型、要请求的 URL、是否要异步请求。- 我们用它
xhttp.send()来发送请求。 - 我们设置了一个方法
onload,该方法会返回responseText请求中的数据。
获取 API
与旧式的 XMLHttpRequest 相比,Fetch 可以更轻松地发出 Web 请求和处理响应。
它使用 Promise,避免了回调地狱,也无需记住 XMLHttpRequest 的复杂 API,因此是一个更简单的 API。
句法
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
我们将 URL 传递给 fetch 函数,该函数会返回一个包含响应的 Promise。然而,这只是一个 HTTP 响应,而不是实际的 JSON 数据。要获取 JSON 内容,我们使用.json响应的 `getJSON()` 方法。最后,我们将数据打印到控制台。
Axios
- Axios 是一个基于 Promise 的 HTTP 客户端,适用于浏览器和 Node.js。
- 这是对fetch API的改进。
安装
Axios 可以通过 CDN 或 node.js 添加:
使用 CDN
我们可以将以下脚本标签附加到 html 文档中我们自己的 js 文件上方,以使用 Axios。
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
使用 npm
$ npm install axios
用法
获取请求
发起 GET 请求非常简单,只需编写 `.getRequest()` 即可axios.get(url)。为了处理响应,我们使用 `.getResponse()` .then(),并且.catch()由于 axios 使用 Promise,因此
我们不需要.then像 fetch API 那样链式调用其他 API。
axios.get('API URL')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
我们还可以使用 async/await。
const main = async ()=>{
try{
let result = await axios.get("url")
return result
}catch{
console.log("request failed");
}
}
让我们使用 Axios 向 GitHub API 发出请求:
axios.get('https://api.github.com/users/mapbox')
.then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
});
// logs:
// => {login: "mapbox", id: 600935, node_id: "MDEyOk9yZ2FuaXphdGlvbjYwMDkzNQ==", avatar_url: "https://avatars1.githubusercontent.com/u/600935?v=4", gravatar_id: "", …}
// => 200
// => OK
// => {x-ratelimit-limit: "60", x-github-media-type: "github.v3", x-ratelimit-remaining: "60", last-modified: "Wed, 01 Aug 2018 02:50:03 GMT", etag: "W/"3062389570cc468e0b474db27046e8c9"", …}
我们还可以使用 Axios 发出其他请求。
axios.request(config)axios.get(url[, config])axios.delete(url[, config])axios.head(url[, config])axios.options(url[, config])axios.post(url[, data[, config]])axios.put(url[, data[, config]])axios.patch(url[, data[, config]])
有些参数用方括号括起来,因为它们是可选的。
API项目
现在,是时候把我们目前所学到的所有知识整合起来了。
这个项目是关于什么的?
这个项目是科尔特·斯蒂尔(Colt Steele)的网页开发者训练营课程的一部分。
它是一个网站,使用tvmaze API根据用户的输入显示电视节目。
代码
以下是已完成项目的链接:TvShowApi
index.html
- 我们创建一个表单,并给它添加一个类,
search-form以便稍后在 JavaScript 中使用。 - 添加文本输入框以获取数据。
- 用于提交表单的提交按钮。
<form class="search-form" >
<input type="text" class="form-control" id="search" placeholder="Search Tvshows">
<input type="submit" class="form-control mt-2" id="sub" value="Submit">
</form>
我们将从 API 获取的图像将显示在此 div 元素内。
<div class="shows">
</div>
app.js
使用查询选择器选择所有必需元素。
let input = document.querySelector("#search");
let showdiv = document.querySelector('.shows')
let searchForm = document.querySelector('.search-form')
- 我们创建了一个事件监听器,每次表单提交时都会异步发出 axios 请求。
- 从 axios 获取数据后,我们将其传递给一个
createImages函数,该函数用于显示我们从 API 返回的不同节目的图像。
searchForm.addEventListener("submit",async (e)=>{ //e is a event object
e.preventDefault(); //Prevents form from refreshing the page.
if(input.value != ""){ // Checking if the input is empty.
try {
let result = await axios.get(`http://api.tvmaze.com/search/shows?q=${input.value}`)
createImages(result.data)//result.data is an array
console.log(result); // You can look at the result from the api in the console
}
catch (error) {
console.log(error);
}
}
})
下面这个 createImages 函数用于根据 API 数据创建图像。
const createImages = (shows)=>{//shows is an array
for(show of shows){
if (show.show.image) { // checking if there is an image for the current show
let image = document.createElement('img')
image.src = show.show.image.medium // show.show.image.medium contains the url of the image
showdiv.append(image) //we attach the images to an empty div that we created in html
}
};
}
下图显示了我们在函数中用于获取图像 URL 的路径createImages。
结论
我希望读完本文后,您能更好地理解异步 JavaScript 这幅拼图的不同组成部分。
接下来,你需要做的就是在自己的代码中尝试不同的概念。你也可以尝试文章中提供的不同代码片段,并观察得到的结果。
如果您觉得这篇文章有用,请在评论区告诉我。
下次见 :)
文章来源:https://dev.to/chinmaymhatre/crash-course-in-asynchronous-javascript-part-2-29jn




