美文网首页
Promise网络请求,异步编程封装,优雅

Promise网络请求,异步编程封装,优雅

作者: 似朝朝我心 | 来源:发表于2020-10-13 15:36 被阅读0次

    1.什么是Promise,Promise到底是做什么的,有什么作用?

    Promise(承诺)是ES6中一个非常重要和好用的特性,Promise是针对异步编程的一种实现解决方案,Promise内部可以用来处理异步事件和操作,即可以用于包裹(wrapper into)封装异步操作代码。

    2.常见的应用场景。

    • 网络请求(网络请求=异步操作=setTimeout())

    比如我们想封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的同步编程一样将结果直接返回。


    如果按照这样排着队来执行代码,同步编程就会带来一个严重的问题,那就是网络阻塞。

    比如说,我们的用户在电商购物平台浏览进行某种操作(这些生鲜水果真不错,我想买几斤,用户在点击某个喜欢的商品,一般都会进一步了解商品的详情,这时候就会想点击了解下关于商品的详情,看看评论,才决定下不下订单)的时候,也就是说,用户的所有这些不确定性点击事件,会突然间要向我们的服务器发送一个网络请求(这时候的JS代码就会去请求有关这个商品的详情页反馈给用户),而网络请求一般情况下呢都是比较耗时的。

    如果这个时候,我们的网络请求是同步(按照顺序排着队执行代码,不许插队)的话,就意味着用户面临网络阻塞的情形(哎哎!兄弟,我在咨询相关的业务办理呢,还没到你呢,到后面排队去,排队去!),这就导致用户界面上什么东西都不会显示,这时候就会出现了网页空白,因为当前的js代码执行已经被阻塞了,它要等到当前网络请求任务完成后,才轮到它。),

    所以这时候呢,我们会传入另外一个函数,在数据请求成功时,在数据请求成功时,将数据通过传入的函数回调回去。

    就是当我们有一个网络请求的时候,我们会直接开启一个异步的任务线程,让这个异步任务专门单独处理我们的请求(相当于我们去银行开办业务的时候,银行业务众多且杂,如果所有业务都集中在一个窗口办理的话,那就非常不现实了,因为有的人想转账,有的人想存钱投资理财(办理几几十分钟),有的人想单纯地办卡,七八分钟就可以办好的业务,那也要让他们安安静静的排队等待一整天?等到大晚上吗?

    如果这时候,采取分门别类的对策,开出一个个专门属于让我们办理具体业务的窗口的话,那让用户办理起业务就方便了,反过来想,这就好比我们的异步编程有专属于我们自己的通道,办卡在1号窗口,转账在2号窗口,这样我们就避免了集中在一个窗口里面排着长长的等侯队列,而直接去1号窗口办理业务即可。)

    但开辟出的1号窗口,如果人太多的话,依然是排队等候呀,没错,这就是所谓的回调地狱了。

    3.Promise请求过多也会带来问题。

    当我们的网络请求非常复杂时,会出现回调地狱的情形,(也就是一个回调函数里面还需要一个回调,回调函数里面又需要一个回调,呈现出多层级嵌套的回调关系。)

    4.网络请求的回调地狱

    我们可以考虑一下下面这种嵌套场景。

    • 我们需要通过一个url1从服务器加载一个数据data1,同时data1中包含了下一个请求的url2
    • 接着,我们需要通过data1取出url2,从服务器加载数据data2,同时data2中还包含了下一个请求的url3
    • 再然后,我们需要通过data2取出url3,从服务器加载一个数据data3,同时data3中又包含了下一个请求的url4
    • 最后发送网络请求url4,获取最终的数据data4


    总结:上面的代码是可以正常运行并且能够获取到我们想要的结果,不过,这样的代码难看且不说,(当代码块足够多的时候)维护起来也是要死的,因此我们需要一种更加优雅的方式来进行这种异步操作,也就是ES6为我们提供的Promise类,可以非常优雅地进行异步操作

    5.Promise的基本语法使用

    使用定时器(setTimeout)来模拟异步操作

    • 以前我们使用定时器的写法(简单的异步操作)


    • 通过Promise类进行封装




    • 现在我们有一个需求:延迟1s打印4次内容,继续延迟1s打印4次内容,载来延迟1s,执行4次打印内容。


    • 有意思的是,执行上面的代码,我们发现,我们自己已经陷入了回调地狱的情形。


    • 所以我们要优雅地封装代码。


    • 上面我们已经初步构建出一个框架,下面让我们把之前的代码封装进去。


    • 这样一看,我们好像也没干什么,确实也是,所以我们需要继续new Promise()进行封装,这个过程其实就是类似处理我们的网路请求。


    • 还有一个setTimeout,意味着还要继续new Promise()进行封装。


      总结:最后的代码呈现出(链式编程)的写法,代码看起来好像是更加复杂了,其实不然,我们可以更加明了的看出Promise封装的层次感,大致上的结构就是呈现:一个Promise,一个then,然后一个Promise,一个then,依此类推下去,就形成清晰的一条链子,环环衔接,这一个链条还执行完毕后,就交给下一个链条。
    • 为了加上印象,我们可以再回过头来看下这种代码。


    • 如果发送的网络请求达到上百次,按照这种写法,估计代码一环套一环写下去,开发者到最后,可能连自己都傻傻分不清楚,这对大括号到底属于谁的,这时候,维护起来就相当困扰。

    6.OK,通过上面的例子,我们已经清晰认识到Promise的基本使用封装写法,那什么情况会用到Promise呢?

    • 一般情况下,当我们有异步操作时,就可以使用Promise对这个异步操作进行封装,通过new 一个Promise类。

    • 源码是这样解释的。



    • 在Promise网络请求中,如resolve成功回调则交给then处理,如reject失败回调则交给catch处理。


    7.Promise的三种状态

    当我们在开发中有异步操作时,就可以给异步操作包装一个Promise网络请求,通过异步操作之后会出现三种状态

    • pending: 等待状态,比如我们正在进行网络请求,或者定时器还在进行,没有到终止时间。
    • fulfill: 满足状态,当我们主动回调了resolve时,代表着请求成功了,就处于该状态,并且会回调.then()函数
    • reject: 即拒绝状态,当我们主动回调了reject的时候,也就意味着网络请求失败了,就处于该状态,并且会回调.catch()函数。

    图解:

    8.Promise的异步处理形式。

    一种是.then()和.catch()结合形式


    另一种直接在.then()回调函数里面操作,即.then(函数1, 函数2)

    9.Promise的链式调用标准。

    其中一种我们已经在上面的例子实现了(用链式编程解决回调地狱的情形),下面我们看下Promise另一种的链式调用标准。

    需求:

    • 我们有个一个网络请求,请求到数据a后,然后自己处理10代码。
    • 对数据a做拼接处理,让他变成数据ab,然后它自己处理10代码。
    • 对原有的ab数据继续做拼接处理,让其变成数据abc,然后也让他处理相关代码。

    代码原型:



    • 优雅简化版,直接通过Promise.resolve(返回结果)。


    • 至简优雅版,直接return返回结果,省略掉Promise.resolve(),因为Promise内部有对应的API接口支持,会自动处理我们的逻辑绕绕。

      总结:上面的例子就是这种链式调用方式的层层演进,大道至简。

    我们上面考虑到的都是网络请求成功后拿到数据的情形,那有没有可能在某一层的网路请求被拒绝pass掉了,导致后面层拿不到数据呢?这时候我们该怎么办呢?


    除了reject()和.catch()搭配使用处理异常外,throw 也可以直接抛出异常。

    10.Promise.all()方法的使用,在某次开发需求中,可能会遇到需要发送2次网络请求的情形,这时候就会用到Promise.all()方法。

    • 上面我们做到的都是分层请求关系(层次感很清晰),但可能有时候需要并列的Promise请求.

    先来看下Ajax是如何操作的。



    ES6中Promise.all()方法如何操作的。



    • 到这里,我们基本了解了Promise.all()方法的使用原理,回到上面的需求实现。


    • 上面的代码是在实际开发会遇到的,但我们没有url地址和安装jQuery,自然无法使用ajax请求数据,只能当伪代码处理,无法测试出实际效果,所以我们可用setTimeout来代替ajax异步操作。



    相关文章

      网友评论

          本文标题:Promise网络请求,异步编程封装,优雅

          本文链接:https://www.haomeiwen.com/subject/qypupktx.html