前言:
在我学习如何使用JS来拉取数据时,发现我虽然会使用fetch(),但是却对其原理并没有什么过多的了解,对其的了解仅限于其是基于promise对象而言,而当我对其搜索时发现最多的概念就是异步编程,所以本文来详细了解一下异步编程与其常见的形式。
异步编程的概念:
异步编程允许程序在等待某个长时间操作完成时,不阻塞或挂起执行的编程方式。
通俗来讲,就是异步编程允许执行某个长时间任务时,程序不需要等待,可以继续执行之后的代码,而当这个长时间任务完成后再来通知我们。
这种编程模式避免了程序的阻塞,也大大提升了CPU的执行效率。
以JavaScript为例的实现方式:
而为了实现异步编程当然少不了核心的几个方式:
一、回调函数:
传统的异步编程形式之一,通过传递一个函数作为参数,在异步操作完成后调用这个函数来处理结果。
例如使用setTimeout定时器来实现让一个函数在指定时间后执行,这样在等待的时候程序会继续运行其后面的代码。
setTimeout(() => {
console.log("兄弟你好香")
}, 2000); // 两秒后执行打印
console.log("兄弟兄弟~")
setTimeout内部的函数本身会立刻返回,所以程序会紧接着执行之后的代码,而回调函数则需要等待指定时间后执行。
虽然回调很好理解,但是其有个明显的缺点
-->如果我们需要执行多个回调函数,那么代码很有可能会变成下面的样子:
setTimeout(() => {
console.log("两秒后~");
setTimeout(() => {
console.log("两秒后~");
setTimeout(() => {
console.log("两秒后~");
// .....
}, 2000);
}, 2000);
}, 2000);
在执行完第一个回调函数后又执行回调函数里面第二个任务,依次嵌套,而最直观的表现就是代码的可读性会变得很差,毕竟一直在向右增长。而这也是我们所熟知的函数的“回调地狱”。
二、Promise:
为了解决回调地狱的问题,promise诞生了。
在 JavaScript 中,promise 是一种处理异步操作的对象,其表示一个尚未完成但最终会完成或失败的操作的结果。每个 promise 都有三种状态:
pending(发出承诺):初始状态,既不是成功也不是失败。
fulfilled(已兑现):操作成功完成。
rejected(已拒绝):操作失败。
而使用promise的APIfetch()就是一个不错的例子,其用于发起一个请求来获取服务器数据。我们可以使用其来动态更新页面的内容,也就是Ajax技术。
当我们使用fetch()去访问一个测试地址的数据,我们可以发现其会返还一个promise对象。
fetch() 的行为遵循Promise(承诺)的概念——其承诺在未来的某个时刻兑现/拒绝提供数据。具体来说:
请求成功时,fetch 会解析为一个 response 对象,表示从服务器收到的响应。
如果请求因为网络问题而失败,promise 可能会被拒绝。(fetch() 只有在网络请求遇到问题时才会返回一个被拒绝的Promise,例如网络不可用、DNS查找失败等)
当 fetch() 的 promise 被解决为成功时,我们可以调用它的.then()方法来处理返还 response 对象并且使用回调函数来实现对数据的操作。
值得注意的是.then() 方法本身也返回一个新的 promise。
javascript 代码解读复制代码
fetch("https://.....")
// 就像常用的将返还内容转换为json对象
// .then(res => res.json)
// 其具体为:
.then((response) => {
// 这里建议手动检查response.ok属性,并手动抛出错误
return response.json(); // 返回一个promise,解析JSON数据完成后被解决
})
正因.then返回一个新的 promise,所以使得其能够进行链式调用,使用一种链式结构来将多个异步操作串联起来,例如:
fetch("https://.....")
.then(res => res.json())
.then(data => console.log(data));
// .then(() -> { ..... })
我们可以使用上述代码将返回的数据转换为JSON格式完成后,再执行打印其结果,当然也可以在这一操作后再执行其他异步操作。
当然执行时可能会遇到一些错误,为了捕获这些错误,我们也可以在其末尾添加一个.catch。这样在之前的任意一个.then发生错误,控制权就会立刻转到.catch的身上。例如:
fetch("https://.....")
.then(res => res.json())
.then(data => console.log(data));
// .then(() -> { ..... })
.catch((error) => {
console.error(err);
});
这样就避免了例如传统回调函数的层层嵌套,转变为了链式的向下增长,这样可读性就大大提升了。
三、async/await:
async/await是新添加的,基于Promise的一种语法糖,使异步代码看起来像同步代码,提高了代码的可读性和简洁性。
其使用方法也很简单,首先我们需要使用async来标记一个函数为异步函数(异步函数的返回值永远是promise对象)。
async function func() {
// .....
}
func(); // 返还值恒为promise对象
我们可以在异步函数中调用其他的异步函数,不过不再需要使用.then(),而是使用await语法,例如:
async function func() {
// 当遇到await时,js引擎会停止async函数的执行,直到await后面的promise被解析
// 并且直接返回结果,所以这里的response已经是服务器返回的数据了
const response = await fetch("http://.....");
const data = await response.json();
console.log(data);
}
func();
看似在此处await停止了函数的执行,但是在等待promise解析的过程中,JS仍然可以处理其它任务。
需要注意的是,await不能单独存在,其是强制依赖于async函数的,其只能在async函数内部存在。