Promise——告别回调函数,常见的web开发技术

封装异步代码,让处理流程变得线性

===============================================================================

由于我们重点关注的是输入内容(请求信息)和输出内容(回复信息),至于中间的异步请求过程,我们不想在代码里面体现太多,因为这会干扰核心的代码逻辑。整体思路如下图所示:

在这里插入图片描述

从图中你可以看到,我们将 XMLHttpRequest 请求过程的代码封装起来了,重点关注输入数据和输出结果。

那我们就按照这个思路来改造代码。首先,我们把输入的 HTTP 请求信息全部保存到一个 request 的结构中,包括请求地址、请求头、请求方式、引用地址、同步请求还是异步请求、安全设置等信息。request 结构如下所示:

//makeRequest用来构造request对象

function makeRequest(request_url) {

let request = {

method: ‘Get’,

url: request_url,

headers: ‘’,

body: ‘’,

credentials: false,

sync: true,

responseType: ‘text’,

referrer: ‘’

}

return request

}

然后就可以封装请求过程了,这里我们将所有的请求细节封装进 XFetch 函数,XFetch 代码如下所示:

//[in] request,请求信息,请求头,延时值,返回类型等

//[out] resolve, 执行成功,回调该函数

//[out] reject 执行失败,回调该函数

function XFetch(request, resolve, reject) {

let xhr = new XMLHttpRequest()

xhr.ontimeout = function (e) { reject(e) }

xhr.onerror = function (e) { reject(e) }

xhr.onreadystatechange = function () {

if (xhr.status = 200)

resolve(xhr.response)

}

xhr.open(request.method, URL, request.sync);

xhr.timeout = request.timeout;

xhr.responseType = request.responseType;

//补充其他请求信息

//…

xhr.send();

}

这个 XFetch 函数需要一个 request 作为输入,然后还需要两个回调函数 resolve 和 reject,当请求成功时回调 resolve 函数,当请求出现问题时回调 reject 函数。

有了这些后,我们就可以来实现业务代码了,具体的实现方式如下所示:

XFetch(makeRequest(‘https://time.geekbang.org’),

function resolve(data) {

console.log(data)

}, function reject(e) {

console.log(e)

})


新的问题:回调地狱

========================================================================

上面的示例代码已经比较符合人的线性思维了,在一些简单的场景下运行效果也是非常好的,不过一旦接触到稍微复杂点的项目时,你就会发现,如果嵌套了太多的回调函数就很容易使得自己陷入了回调地狱,不能自拔。你可以参考下面这段让人凌乱的代码:

XFetch(makeRequest(‘https://time.geekbang.org/?category’),

function resolve(response) {

console.log(response)

XFetch(makeRequest(‘https://time.geekbang.org/column’),

function resolve(response) {

console.log(response)

XFetch(makeRequest(‘https://time.geekbang.org’)

function resolve(response) {

console.log(response)

}, function reject(e) {

console.log(e)

})

}, function reject(e) {

console.log(e)

})

}, function reject(e) {

console.log(e)

})

这段代码是先请求time.geekbang.org/?category,如果请求成功的话,那么再请求time.geekbang.org/column,如果再次请求成功的话,就继续请求time.geekbang.org。也就是说这段代码用了三层嵌套请求,就已经让代码变得混乱不堪,所以,我们还需要解决这种嵌套调用后混乱的代码结构。

这段代码之所以看上去很乱,归结其原因有两点:

  • 第一是嵌套调用,下面的任务依赖上个任务的请求结果,并在上个任务的回调函数内部执行新的业务逻辑,这样当嵌套层次多了之后,代码的可读性就变得非常差了。

  • 第二是任务的不确定性,执行每个任务都有两种可能的结果(成功或者失败),所以体现在代码中就需要对每个任务的执行结果做两次判断,这种对每个任务都要进行一次额外的错误处理的方式,明显增加了代码的混乱程度。

原因分析出来后,那么问题的解决思路就很清晰了:

  • 第一是消灭嵌套调用;

  • 第二是合并多个任务的错误处理。

这么讲可能有点抽象,不过 Promise 已经帮助我们解决了这两个问题。那么接下来我们就来看看 Promise 是怎么消灭嵌套调用和合并多个任务的错误处理的。


Promise:消灭嵌套调用和多次错误处理

====================================================================================

首先,我们使用 Promise 来重构 XFetch 的代码,示例代码如下所示:

function XFetch(request) {

function executor(resolve, reject) {

let xhr = new XMLHttpRequest()

xhr.open(‘GET’, request.url, true)

xhr.ontimeout = function (e) { reject(e) }

xhr.onerror = function (e) { reject(e) }

xhr.onreadystatechange = function () {

if (this.readyState === 4) {

if (this.status === 200) {

resolve(this.responseText, this)

} else {

let error = {

code: this.status,

response: this.response

}

reject(error, this)

}

}

}

xhr.send()

}

return new Promise(executor)

}

接下来,我们再利用 XFetch 来构造请求流程,代码如下:

var x1 = XFetch(makeRequest(‘https://time.geekbang.org/?category’))

var x2 = x1.then(value => {

console.log(value)

return XFetch(makeRequest(‘https://www.geekbang.org/column’))

})

var x3 = x2.then(value => {

console.log(value)

return XFetch(makeRequest(‘https://time.geekbang.org’))

})

x3.catch(error => {

console.log(error)

})

你可以观察上面这两段代码,重点关注下 Promise 的使用方式。

  • 首先我们引入了 Promise,在调用 XFetch 时,会返回一个 Promise 对象。

  • 构建 Promise 对象时,需要传入一个 executor 函数,XFetch 的主要业务流程都在 executor 函数中执行。

  • 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。

  • 在 excutor 函数中调用 resolve 函数时,会触发 promise.then 设置的回调函数;而调用 reject 函数时,会触发 promise.catch 设置的回调函数。

以上简单介绍了 Promise 一些主要的使用方法,通过引入 Promise,上面这段代码看起来就非常线性了,也非常符合人的直觉,是不是很酷?基于这段代码,我们就可以来分析 Promise 是如何消灭嵌套回调和合并多个错误处理了。

我们先来看看 Promise 是怎么消灭嵌套回调的。产生嵌套函数的一个主要原因是在发起任务请求时会带上回调函数,这样当任务处理结束之后,下个任务就只能在回调函数中来处理了。

Promise 主要通过下面两步解决嵌套回调问题的。

**首先,Promise 实现了回调函数的延时绑定。**回调函数的延时绑定在代码上体现就是先创建 Promise 对象 x1,通过 Promise 的构造函数 executor 来执行业务逻辑;创建好 Promise 对象 x1 之后,再使用 x1.then 来设置回调函数。示范代码如下:

//创建Promise对象x1,并在executor函数中执行业务逻辑

function executor(resolve, reject){

resolve(100)

}

let x1 = new Promise(executor)

//x1延迟绑定回调函数onResolve

function onResolve(value){

console.log(value)

}

x1.then(onResolve)

其次,需要将回调函数 onResolve 的返回值穿透到最外层。因为我们会根据 onResolve 函数的传入值来决定创建什么类型的 Promise 任务,创建好的 Promise 对象需要返回到最外层,这样就可以摆脱嵌套循环了。你可以先看下面的代码:

在这里插入图片描述

现在我们知道了 Promise 通过回调函数延迟绑定和回调函数返回值穿透的技术,解决了循环嵌套。

那接下来我们再来看看 Promise 是怎么处理异常的,你可以回顾上篇文章思考题留的那段代码,我把这段代码也贴在文中了,如下所示:

function executor(resolve, reject) {

let rand = Math.random();

console.log(1)

console.log(rand)

if (rand > 0.5)

resolve()

else

reject()

}

var p0 = new Promise(executor);

var p1 = p0.then((value) => {

console.log(“succeed-1”)

return new Promise(executor)

})

var p3 = p1.then((value) => {

console.log(“succeed-2”)

return new Promise(executor)

})

var p4 = p3.then((value) => {

console.log(“succeed-3”)

return new Promise(executor)

})

p4.catch((error) => {

console.log(“error”)

})

console.log(2)

这段代码有四个 Promise 对象:p0~p4。无论哪个对象里面抛出异常,都可以通过最后一个对象 p4.catch 来捕获异常,通过这种方式可以将所有 Promise 对象的错误合并到一个函数来处理,这样就解决了每个任务都需要单独处理异常的问题。

之所以可以使用最后一个对象来捕获所有异常,是因为 Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 onReject 函数处理或 catch 语句捕获为止。具备了这样“冒泡”的特性后,就不需要在每个 Promise 对象中单独捕获异常了。至于 Promise 错误的“冒泡”性质是怎么实现的,就留给你课后思考了。

通过这种方式,我们就消灭了嵌套调用和频繁的错误处理,这样使得我们写出来的代码更加优雅,更加符合人的线性思维。


Promise 与微任务

===========================================================================

讲了这么多,我们似乎还没有将微任务和 Promise 关联起来,那么 Promise 和微任务的关系到底体现哪里呢?

我们可以结合下面这个简单的 Promise 代码来回答这个问题:

function executor(resolve, reject) {

resolve(100)

}

let demo = new Promise(executor)

function onResolve(value){

console.log(value)

}

demo.then(onResolve)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!

自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。

I3PCke1-1712198958092)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-L87AEUY4-1712198958092)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

[外链图片转存中…(img-UFEIxVwc-1712198958093)]

[外链图片转存中…(img-DeRABUKp-1712198958093)]

资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!

自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值