javaScript 进阶之路 --- 《手写“回调地狱”》(一)

简介: javaScript 进阶之路 --- 《手写“回调地狱”》

image.png

手写“回调地狱”


前言: 可能初次看到这个标题,你会有些惊讶。我们不是要实现“手写 Promise ”吗?怎么变成了手写“回调地狱”了?“我老早看视频学习的时候就知道,我们要避免写成“回调地狱的格式,怎么到你这还要手写这玩意?博主你老标题党了...”

我相信有很多学习前端的小伙伴百分百遇到过这样的面试题:

---“为什么我们要用 Promise 去代替传统的回调函数?”

我相信有很多人都可以随口回答出:“为了避免回调地狱,因为回调地狱会带来xxx的后果....

ok,那么现在我问你,假设现在面试官让你实现一个 “回调地狱”。你脑子里的代码会是怎样的呢?我建议你停下来思考三分钟🤔...

不要问为什么有这么令人无语的问题,因为这就是我实实在在的面试题之一。起初我觉得面试官在刁难我,然而当我真正理解了这个知识点以后,我非常感谢那位面试官,在去研究这个面试题答案的过程中,让我对 JS 有了更深层次的理解...所以在手写 Promise 之前,我希望你能先完成手写 回调地狱

往下阅读之前,请自觉领取并完成阅读本文的前置任务(0/2)

一. 手写回调地狱


我们假设现在有这样的一个场景:
我们前端通过<input/>框,获取到了用户输入的年龄以后,前端需要把这个年龄数值传递给后端。然后后端拿到这个年龄数据经过处理计算,会在 1s 后返回一个年龄 +1 的结果给我们。

image.png

经过上篇的知识可得到,我们按上面的写法,result 将会是 undefined。我们如果想要拿到正确的数值,就需要给 addUserAge 传递一个回调函数。

所以正确的写法应该是这样:

image.png

好的,现在我们拿到了后端传递给我们的第一次数据。

97528812dee0405080e0e1017537e18d_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

接下来,我们所有的逻辑代码都需要在这个回调函数内部去写。

image.png

什么意思呢?凡事需要用到 age+1 这个值的代码,都需要写进 addUserAge 函数的回调函数里。

我们更具体的表现出上面这句话的含义。假设下面的某个页面场景,又需要向后端发起一次请求,让后端再把用户的年龄 +1,我们的页面才能呈现出正确的样式。这怎么办呢?
----注意: 这里为了方便区分,我们把第一次拿到的结果写为 result1 然后赋值给局部变量 age1,第二次的结果写为 result2,赋值给 age2,以此类推。

image.png

我们可以看到,第一秒返回了 数字2,再过一秒返回了 数字31b929e7ca20b4e4f83720b4cdc0df786_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

(tips:我这里再次提示,如果你这一步没看懂,我真心希望你们回过头先去看我另外两篇支线任务的内容再来往下继续看。因为这里是一个难点,确实不是第一次看就能直接明白什么意思的,有难度的知识往往都是基础知识堆积而成的,一定要脚踏实地慢慢来)

ok,我们两次的结果都正确拿到了。但是!我想你已经猜到了,我们在实际开发中,请求绝对不只两次。后面的页面,又又又需要我们再次将年龄 +1 然后才能正确展示,怎么办呢?注意!你后面的结果都是依赖上一步的结果进行的,所以我们又需要传递一个回调函数给 addUserAge 。可想而知,我们的后面的逻辑又只能在第三个 addUserAge 的回调函数内书写。

image.png

结果如下:

8da497f619244c6e919b49c09a0a249d_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

聪明的你也可能猜到,同理,后面的某个页面需要拿到 age+6 的结果怎么办呢?也就是我需要调用 addAge 函数 6 次,我们的代码结构就会变成下面的这个样子

image.png

fec7d94aee37404db4e4b1b185672dd9_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

上面的代码是我为了清晰的展示才调整的空行,假如我们现在没有空行。

image.png

看到黄色的金字塔了吗?这就是我们俗称的回调地狱(又称死亡金字塔)的由来。 你会发现这种代码读起来是真的又臭又长,可读性极差,可维护性极差。这还仅仅只是一个小的功能页面就已经堆叠成这样了,我们还没有做任何复杂的逻辑运算。

二. Promise 的出现


如你所见,用上面的“回调地狱”写法写出来的代码,毫不夸张的说就是屎山。你敢这样写,公司就不敢辞退你。因为这代码只有你能看懂!哈哈,开个笑话,接下来的日子里你看的懂还好,最糟糕的情况就是过了两天有可能连你自己都看不懂这坨代码了...🍦

这时候就迫切的需要一种解决方法来避免上面的书写方式。接下来有请我们的重量级嘉宾 Promise。👏我们先看这个单词的意思是什么。

image.png

承诺,保证”我们需要先理清设计者为什么要用 “承诺” 来表达这个构造函数。

⚠️注意:

这里的“承诺”并不是指我保证给你一个“成功”的答复,而是指“不管成功还是失败”我都会通知你。更人性化的表达方式就是:我一定会给你一个答复,而不是我一定会给你一个“满意”的答复。

如果你明白了上面的这句话。那么接下来让我们通过写代码加深一下理解。首先 Promise 是一个类,那么们就可以通过 new 去调用。

image.png

看到报错了吗?我们先看一下错误信息。

image.png

它好像提示我们少了一个参数,参数的名字叫 executor。那这个executor 又是个什么呢?🤔

真的不用太害怕,它就是一个普通的函数起了个洋气的名字而已。并且它是作为了参数传递给你 Promise 构造函数,那么它就是一个普通的回调函数而已。(真的,它就是一个普普通通的函数而已,不要把它想的太过神奇了。)

OK,你说我少给你一个函数作为参数,那我给你不就行了吗?

image.png

好像确实没报错了。但是你是不是忽略了什么事情?我们回过头看一下这个回调函数的介绍。

image.png

英语不好的同学我强烈建议你去搜一下这段话的原意,我在这里简单的表达一下大概意思。

这个回调函数会用来初始化这个 promise 实例。这个回调函数会被传递两个参数,(is passed 注意这里需要理解是“被动语态”,这里是会“被传递”的意思。)一个叫 resolve的回调函数 一个叫 reject的回调函数

可以得到下面的写法。

image.png

这里需要特别注意,在这里 resolvereject 是实参而不是形参。什么意思呢?意思就是它是可以直接被调用的。它是被 Promise 传递过来的,形参是在 Promise 类里定义的。这点我们会在后面的《手写 Promise》 里解释

image.png

好像还是有错,我们再看看。

image.png

这里它表达的意思不是特别好理解,我来简单解释一下。

resolve 函数的参数,就是我们之前去请求后端获得的那个返回值

还记得我们之前的写法吗,我们是拿不到 result

image.png

接下来我们换一种写法,把它改造成 Promise 的写法。

image.png

注意:我们在这里为了模拟数据过了一会才能回来的场景,在实际项目中,setTimeout 那段函数其实就是我们向后端发请求的函数。


相关文章
|
12天前
|
XML 前端开发 JavaScript
JavaScript进阶 - AJAX请求与Fetch API
【7月更文挑战第3天】前端开发中的异步基石:AJAX与Fetch。AJAX,使用XMLHttpRequest,处理跨域、回调地狱和错误处理。Fetch,基于Promise,简化请求,但需注意默认无跨域头和HTTP错误处理。两者各有优劣,理解其问题与解决策略,能提升前端应用的性能和用户体验。
|
4天前
|
前端开发 JavaScript 安全
JavaScript进阶-JavaScript库与框架简介
【7月更文挑战第11天】JavaScript库和框架加速Web开发,但也带来挑战。选择适合项目、团队技能的库或框架,如React、Angular、Vue,是关键。保持依赖更新,注意性能优化,避免过度依赖。遵循最佳实践,确保安全性,如防XSS和CSRF。学习基础,结合代码示例(如React计数器组件),提升开发效率和应用质量。
|
10天前
|
资源调度 JavaScript 前端开发
JavaScript进阶 - JavaScript库与框架简介
【7月更文挑战第5天】JavaScript库和框架构成了前端开发的核心,如jQuery简化DOM操作,Angular、React和Vue提供全面解决方案。选择时要明确需求,避免过度工程化和陡峭学习曲线。使用版本管理工具确保兼容性,持续学习以适应技术变化。示例展示了jQuery和React的简单应用。正确选择和使用这些工具,能提升开发效率并创造优秀Web应用。
|
11天前
|
缓存 JavaScript 前端开发
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第4天】JavaScript的Web Workers和Service Worker增强了Web性能。Web Workers处理后台多线程,减轻主线程负担,但通信有开销,受同源策略限制。Service Worker则用于离线缓存和推送通知,需管理其生命周期、更新策略,并确保安全。两者都带来了挑战,但也极大提升了用户体验。通过理解和优化,开发者能构建更高效、安全的Web应用。
|
13天前
|
存储 前端开发 安全
JavaScript进阶 - 浏览器存储:localStorage, sessionStorage, cookies
【7月更文挑战第2天】探索Web存储:localStorage持久化,sessionStorage会话限定,cookies则伴随HTTP请求。了解它们的特性和限制,如localStorage的5MB容量限制、跨域问题,sessionStorage的生命周期,及cookies的安全与带宽消耗。使用时需权衡安全、效率与应用场景。示例代码展示存储与检索方法。
|
5天前
|
缓存 前端开发 JavaScript
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第10天】在Web开发中,Web Workers和Service Worker提升性能。Workers运行后台任务,防止界面冻结。Web Workers处理计算密集型任务,Service Worker则缓存资源实现离线支持。常见问题包括通信故障、资源限制、注册错误及缓存更新。通过示例代码展示了两者用法,并强调生命周期管理和错误处理的重要性。善用这些技术,可构建高性能的Web应用。
|
6天前
|
XML 前端开发 JavaScript
JavaScript进阶 - AJAX请求与Fetch API
【7月更文挑战第9天】JavaScript进阶:AJAX与Fetch API对比。AJAX用于异步数据交换,XMLHttpRequest API复杂,依赖回调。Fetch API是现代、基于Promise的解决方案,简化请求处理。示例:`fetch(&#39;url&#39;).then(r =&gt; r.json()).then(data =&gt; console.log(data)).catch(err =&gt; console.error(err))`。注意点包括检查HTTP状态、错误处理、CORS、Cookie和超时。Fetch提高了异步代码的可读性,但需留意潜在问题。
|
7天前
|
存储 JavaScript 前端开发
JavaScript进阶 - 浏览器存储:localStorage, sessionStorage, cookies
【7月更文挑战第8天】Web开发中的客户端存储技术,如`localStorage`, `sessionStorage`和`cookies`,用于保存用户设置和跟踪活动。`localStorage`持久化存储,`sessionStorage`随页面会话消失。两者提供基本的增删查改操作,但有大小限制和安全风险。`cookies`适合会话管理,可设置过期时间并能跨域。使用时注意存储量、安全性和跨域策略,选择适合场景的存储方式。
|
7天前
|
设计模式 JavaScript 前端开发
JavaScript进阶 - JavaScript设计模式
【7月更文挑战第7天】在软件工程中,设计模式是解决常见问题的标准解决方案。JavaScript中的工厂模式用于对象创建,但过度使用可能导致抽象过度和缺乏灵活性。单例模式确保唯一实例,但应注意避免全局状态和过度使用。观察者模式实现了一对多依赖,需警惕性能影响和循环依赖。通过理解模式的优缺点,能提升代码质量。例如,工厂模式通过`createShape`函数动态创建对象;单例模式用闭包保证唯一实例;观察者模式让主题对象通知多个观察者。设计模式的恰当运用能增强代码可维护性。
|
9天前
|
缓存 JavaScript 前端开发
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第6天】JavaScript的Web Workers和Service Worker增强了浏览器的性能处理和离线功能。Web Workers处理后台计算,减轻主线程压力,但通信有开销,受同源策略限制。Service Worker则能拦截网络请求,支持离线缓存和推送通知,但其生命周期和权限管理需谨慎处理。通过理解它们的工作原理和限制,开发者能创建更流畅、更健壮的Web应用。