网络请求 - 异步编程详解

一、概述

网络管理模块主要提供以下功能:

  • HTTP数据请求:通过HTTP发起一个数据请求。
  • WebSocket连接:使用WebSocket建立服务器与客户端的双向连接。
  • Socket连接:通过Socket进行数据传输。

HTTP和WebSocket都是啥?

网络请求 - 异步编程详解_第1张图片

        比如我们去逛某宝的商品列表,从HTTP协议的角度来看,前端发送了一次HTTP请求,网站返回一次HTTP响应。不过从始至终服务器都不会主动给客户端发送消息请求(就像你喜欢的人从来不会主动找你一样),这就是HTTP协议的特点。

网络请求 - 异步编程详解_第2张图片

        又比如我们玩传奇一刀999的网页游戏,我们甚至全程都没有点一次鼠标,但是服务器就源源不断地将怪物的移动数据和攻击数据发给我们。这种服务器可以主动给客户端发送消息的可双向传输数据场景就是使用了WebSocket协议。

        至于Socket协议,套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。它偏向于底层,与WebSocket的关系就如Java和JavaScript,可以说是除了名字相似之外没什么关系。


二、异步Promise

1、并发、并行、同步和异步

并发

并发是一个比较宽泛的概念,它单纯代表计算机可以执行多项任务

比如一个单核处理器,计算机可以通过分配时间片的方式,让一个任务运行一段时间后切换到另一个任务。不同的任务就这样交替反复的一直执行下去。这个过程也被称作是进程或者线程的上下文切换

并行 

对于多核处理器,计算机可以在不同的核心上真正的并行执行任务,而不用像分配时间片这样的伪同时执行任务

网络请求 - 异步编程详解_第3张图片

 

同步&异步

至于同步和异步,它们则是两种不同的编程模型

同步代表需要等到前一个任务完成之后才可以执行下一个任务,因此在同步中并没有并发或者并行的概念

网络请求 - 异步编程详解_第4张图片

异步则代表不同的任务之间并不会相互等待先后执行,也就是说执行任务A时也可以执行任务B

网络请求 - 异步编程详解_第5张图片

一个典型的实现异步的方式就是多线程编程(如Java)

但是对于某些编程语言如JavaScript它们本身是没有多线程概念的,不过通过它的函数回调(function callback)机制,依旧可以做到单线程的并发

网络请求 - 异步编程详解_第6张图片

比如在这个案例中,fetch函数用来获取资源,他并不是按照函数编写的顺序去等待addImage()方法执行完毕再输出console.log(),而是跳过了该方法继续执行下面的代码。当获取到资源之后回调函数才被执行。

网络请求 - 异步编程详解_第7张图片

 

 注意:再次强调,JavaScript从设计之初就是一个单线程的语言,这样虽然看起来是同时运行的,但其底层还是运行在一个线程上运行的并发。

不过虽然只有单个线程在执行,不过这种单线程的异步编程还是有不少优点的

网络请求 - 异步编程详解_第8张图片 所有的操作都运行在同一个线程中,你无需考虑线程同步或资源竞争的问题,从源头上避免了多线程之间的频繁切换,降低了线程自身的开销。

2、回调函数

在JavaScript中有两种实现异步的方式,第一种就是回调函数

什么是回调函数?其实很简单,就是一个函数被当成参数被另一个函数调用而已

比如:网络请求 - 异步编程详解_第9张图片

其缺点也很明显,比如setTimeout是js自带的一个定时器,可以在其中传入一个回调函数

网络请求 - 异步编程详解_第10张图片

假如我现在想要在回调函数中继续调用回调函数,就会变成这样 

网络请求 - 异步编程详解_第11张图片 这种回调函数嵌套的代码使得整段代码变得极其的诡异和难看,这就叫回调地狱(Callback Hell)

为了解决这个问题Promise应运而生

3、Promise

Promise也是第二种实现异步的方式

在JavaScript中,Promise对象是用来处理异步操作的一种机制,它表示一个异步操作的最终完成或失败,并且可以链式地处理这些操作。Promise对象有三个状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

let myPromise = new Promise((resolve, reject) => {
  // 异步操作,可以是Ajax请求、文件读取等
  // 如果操作成功,调用 resolve(value)
  // 如果操作失败,调用 reject(reason)
});

myPromise.then((result) => {
  // 处理操作成功的情况
}).catch((error) => {
  // 处理操作失败的情况
});

在创建Promise对象时,你会得到一个具有reslove和reject两个参数的函数。这两个参数都是函数,用于改变Promise对象的状态。具体来说:

  • resolve函数用于将Promise对象从pending(进行中)变为fulfilled(已成功)。

通常,当异步操作成功完成时,就调用该函数,结束异步请求并传递操作的结果作为参数

const myPromise = new Promise((resolve, reject) => {
  // 异步操作成功
  resolve("操作成功");
});
  • reject函数用于将Promise对象的状态从pending(进行中)变为rejected(已失败)。

当异步操作发生错误或失败时,你会调用该函数,结束异步请求并传递错误信息作为参数

const myPromise = new Promise((resolve, reject) => {
  // 异步操作失败
  reject("发生错误");
});

所以也就是说,如果不在异步调用中使用这两个函数的话,当你在别的地方调用这个异步请求时它会一直处于进行中状态,这两个异步请求自然也是是没有返回值的,因此也不会执行后面的回调逻辑。

这里再次强调一遍:Promise本身是无法中止的,其本身只是一个状态机,存储三个状态(pending,resolved,rejected),一旦发出请求了,必须闭环,无法取消。如果你不使用resolve/reject函数那么这个 Promise 将永远保持在 pending 状态。这样的 Promise 并不会自动转变为 fulfilled 或 rejected。

 4、async和await

简单来说,async和await是基于Promise的一个语法糖,可以让异步操作更加简单明了。所以也就是说在理论上,async/await 语法与 Promise 的链式写法在性能上没有本质的区别。它们都基于 JavaScript 的异步执行模型,实际性能差异较小。

不过好处当然是有的,不然还用它干什么

简而言之,使用async和await可以替换Promise的链式写法,让异步代码写的看起来像同步代码一样;替换掉.then,而.catch就用try...catch取代掉

不同于Promise的链式写法,写在async/await中想要中断程序就很简单了,因为语义化非常明显,其实就和一般的function写法一样,想要中断的时候,直接return一个值就行,null,空,false都是可以的。

比如这段Promise的链式操作的TypeScript代码

网络请求 - 异步编程详解_第12张图片

使用了async与await语法糖之后就变成了

网络请求 - 异步编程详解_第13张图片

是不是感觉就像以前写的同步代码一样,一下就顺眼起来了

那它是怎么实现的呢?

他们俩是一对好夫妻,缺一不可,async必须声明的是一个function,如果声明别的await就会很生气觉得它是渣男,就不生效了(报错)

async声明的函数无论如何都会返回一个Promise,比如

网络请求 - 异步编程详解_第14张图片

这是因为async将它自动解析成了Promise.resolve(),也就是说,相当于这段代码的效果

网络请求 - 异步编程详解_第15张图片

await是可以提供等同于"同步效果"的等待异步返回能力的语法糖

顾名思义,就是等一会。等什么?等一个Promise的异步返回,只要await声明的函数还没有返回数据,那么下面的程序是不会去执行的当这个Promise有了返回值,await的结果才会被取得,程序才会接着向下运行。

不过有人常说有了async/await的出现淘汰了Promise,这是错误的。两者是相辅相成的,缺一不可,你可以同时使用这两种语法,或者你更喜欢哪种语法就使用哪种语法

你可能感兴趣的:(网络)