我没有很系统地学过前端,在写前端的时候如果只是单个的网络请求还好,用fetch、axios或者直接用XMLHttpRequest写起来也不复杂。
但是如果是多个请求按顺序拉取数据那可要了我老命了😂,我只会最简单的回调函数的写法,就像下面这些代码:
const requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch('https://xxx.yyy.com/api/zzz/', requestOptions)
.then(response => response.text())
.then(result => {
const data = JSON.parse(result)
fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions)
.then(response => response.text())
.then(result => {
const data = JSON.parse(result)
})
.catch(error => console.log('error', error));
})
.catch(error => console.log('error', error));
假设我需要两步获取一个数据,如从https://xxx.yyy.com/api/zzz/
获取一个数据对象data
,之后通过data.id
得到我要获取数据的序号,最后再发一次请求得到想要的数据。
用回调的方式就类似于上面这样,太繁琐了,而且容易出错,并且一旦逻辑复杂就不好改啦😭。
今天就花些时间好好学一下这块的知识,摆脱回调地狱👻
一、XMLHttpRequest
通过XMLHttpRequest对象创建网络请求的套路如下:
// 假设访问http://localhost:3000/user返回json对象{"name":"Jiangda"}
const xhr = new XMLHttpRequest();
const url = 'http://localhost:3000/user'
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
let json=JSON.parse(xhr.responseText)
let name=json.name
console.log(name)
}
}
xhr.open('GET',url)
xhr.send()
首先创建一个XMLHttpRequest
对象xhr
,然后xhr.onreadystagechange
监听“状态改变”事件,之后xhr.open
初始化请求,最后xhr.send
发送请求。
请求发送后,当浏览器收到响应时就会进入xhr.onreadystagechange
的回调函数中去,首先if (this.readyStage == 4 && this.status == 200)
判断readyStage
是不是4
,在整个请求过程中,xhr.onreadystagechange
会触发四次,每次readyStage
都会自增,从1一直到4,只有到了最后阶段也就是readyStage
为4
时我们才能得到最终的响应数据,同时还要根据status
判断响应的状态码是否正常,通常响应码为2开头说明请求没有遇到问题,最终在控制台上会打出Jiangda
。
可以看出,通过XMLHttpRequest
来处理请求的话,回调函数的书写很麻烦。
二、Promise
如果一个事件依赖于另一个事件的返回结果,那么使用回调会使代码变得很复杂。Promise
对象提供了检查操作失败或成功的一种模式。如果成功,则会返回另一个Promise
。这使得回调的书写更加规范。
Promise
处理的套路如下:
const promise = new Promise((resolve,reject)=>{
let condition = true;
if (condition) {
resolve("ok")
} else {
reject("failed")
}
}).then( msg => console.log(msg))
.catch( err => console.error(err))
.finally( _ =>console.log("finally"))
上面这段代码把整个处理过程串起来了,首先创建一个Promise
对象,它的构造器接收一个函数,函数的第一个参数是没出错时要执行的函数resolve
,第二个参数是出错后要执行的函数reject
。
我的理解是resolve
和reject
都只是个代称,resolve
代指成功后then
里面的回调函数,reject
代指失败后catch
里执行的回调函数。最后的finally
是不论成功失败都会执行的,可以用来做一些收尾工作。
基于Promise
的网络请求可以用axios
库以及浏览器自带的fetch
实现。
axios
库创建请求的套路如下:
import axios from 'axios'
const url = 'http://xxx.yyy.com/'
axios.get(url)
.then(data => console.log(data))
.catch(err => console.error(err))
我比较喜欢用fetch
,它不需要导库,fetch
创建请求的方式和axios
类似,在开头已经展示过了就不重复写了。
虽然Promise
把回调函数的编写方式简化了一些,但还是没有摆脱回调地狱,多个请求串起来的话就会像我开头写的那样,在then
里面创建新的Promise
,最终变成Promise
地狱😂
三、async/await
async/await
可以简化Promise
的写法,使得代码中的函数虽然是异步的但执行顺序是同步的,并且在代码的书写上也和同步的差不多,易于理解。
直接拿MDN(https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await)上的例子说明吧:
直接用fetch
获取数据:
fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
async/await
改写后:
async function myFetch() {
let response = await fetch('coffee.jpg');
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
改写后的代码是不是就很清楚了,没有那么多的then
跟在后面了,这样如果有一连串的网络请求也不用怕了😁
当async
放在一个函数的声明前时,这个函数就是一个异步函数,调用该函数会返回一个Promise
。
await
用于等待一个Promise
对象,它只能在异步函数中使用,await
表达式会暂停当前异步函数的执行,等待 Promise 处理完成。