Promise——告别回调函数(1),CSS浮动的使用与清除


新的问题:回调地狱

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

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

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)

对于上面这段代码,我们需要重点关注下它的执行顺序。

首先执行 new Promise 时,Promise 的构造函数会被执行,不过由于 Promise 是 V8 引擎提供的,所以暂时看不到 Promise 构造函数的细节。

接下来,Promise 的构造函数会调用 Promise 的参数 executor 函数。然后在 executor 中执行了 resolve,resolve 函数也是在 V8 内部实现的,那么 resolve 函数到底做了什么呢?我们知道,执行 resolve 函数,会触发 demo.then 设置的回调函数 onResolve,所以可以推测,resolve 函数内部调用了通过 demo.then 设置的 onResolve 函数。

不过这里需要注意一下,由于 Promise 采用了回调函数延迟绑定技术,所以在执行 resolve 函数的时候,回调函数还没有绑定,那么只能推迟回调函数的执行。

这样按顺序陈述可能把你绕晕了,下面来模拟实现一个 Promise,我们会实现它的构造函数、resolve 方法以及 then 方法,以方便你能看清楚 Promise 的背后都发生了什么。这里我们就把这个对象称为 Bromise,下面就是 Bromise 的实现代码:

function Bromise(executor) {

var onResolve_ = null

var onReject_ = null

//模拟实现resolve和then,暂不支持rejcet

this.then = function (onResolve, onReject) {

onResolve_ = onResolve

};

function resolve(value) {

//setTimeout(()=>{

onResolve_(value)

// },0)

}

executor(resolve, null);

}

观察上面这段代码,我们实现了自己的构造函数、resolve、then 方法。接下来我们使用 Bromise 来实现我们的业务代码,实现后的代码如下所示:

function executor(resolve, reject) {

resolve(100)

}

//将Promise改成我们自己的Bromsie

let demo = new Bromise(executor)

function onResolve(value){

console.log(value)

}

demo.then(onResolve)

执行这段代码,我们发现执行出错,输出的内容是:

Uncaught TypeError: onResolve_ is not a function

at resolve (:10:13)

at executor (:17:5)

at new Bromise (:13:5)

at :19:12

之所以出现这个错误,是由于 Bromise 的延迟绑定导致的,在调用到 onResolve_ 函数的时候,Bromise.then 还没有执行,所以执行上述代码的时候,当然会报“onResolve_ is not a function“的错误了。

也正是因为此,我们要改造 Bromise 中的 resolve 方法,让 resolve 延迟调用 onResolve_。

要让 resolve 中的 onResolve_ 函数延后执行,可以在 resolve 函数里面加上一个定时器,让其延时执行 onResolve_ 函数,你可以参考下面改造后的代码:

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

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

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

img

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

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

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

最后的最后

面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。
需要完整面试题的朋友可以点击蓝色字体免费获取

大厂面试题

面试题目录

真正体系化!**

[外链图片转存中…(img-k5ookziC-1712198924912)]

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

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

最后的最后

面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。
需要完整面试题的朋友可以点击蓝色字体免费获取

[外链图片转存中…(img-cmDlF6qV-1712198924913)]

[外链图片转存中…(img-PLjMhpv9-1712198924913)]

[外链图片转存中…(img-YfECmvV2-1712198924913)]

[外链图片转存中…(img-tadwZeIU-1712198924914)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值