因
如果你和我一样,刚开始编程的时候学习的并非Javascript,而是类似于Java / C# / C++这些类似的语言。那么你在初写js(或泛前端应用)的时候,应该会和我遇到相似的疑问。那就是,我期望的代码执行顺序为什么和实际上的代码执行顺序不相同呢?
1 | // Javascript |
我们知道,程序在读文件的时候是一行一行的读取的。(别问为啥,你也可以写个解释器先读取2n行,再读2n-1行)
然而js中的表现却和我们的预期不一致,做个对比,我们拿Python重复一遍上述的逻辑。
1 | # Python |
Python的逻辑符合我们的“直觉”,即代码总是按照码农给定的顺序执行。
但为啥就你js搞特殊?因为早期的js主要运行在浏览器,负责的工作则是对用户的输入(鼠标点击、移动、悬浮或键盘事件)进行反馈。注定了其代码不能是同步执行的,而是监听式的,即代码块在等待一个时机被执行,而这个执行时机程序员大概率是无法预知的。
虽然一个异步程序的执行时机无法预知,但我们可以使用回调提前对数据进行处理。例如:
1 | function doSomethingFeature (callback) { |
回调地狱
我们用回调函数来处理一个个事件,于是代码中的括号也越来越多。
- 鲁迅
想象一下:网页中的一个弹窗,它有一个按钮,在这个按钮被按下时,浏览器开始向远程服务器获取数据,当数据到达后,网页中的标题变了。由于每一次发生的事件都要依赖于上一次事件(因为不触发事件就无法获取数据), 用回调函数进行数据处理就会变成:
1 | openCustomWindow(()=>{ |
这样的代码,不仅难以阅读,而且维护时多一个括号少一个括号的也很容易让人心态崩溃,只能用用编辑器的智能提示勉强混混这样子。
链式调用
那如果有一种写法,可以让被依赖的数据像自来水管流动一样就好了。按照顺序地从自然水源中经由水厂消毒,在流向小区供水中心,再流向你家的净水器,最后从水龙头流入你的杯子里。
把上面的列子用链式调用的方案改写一下,就得到一个类似于下面的东西:
1 | // js伪代码:理想状态的链式调用模式 |
2003年JQuery横空出世,当时的JQuery就采用了这种做法,从而大大地提高了前端代码的可读性,加之提供完整的类库功能和简单的学习曲线,使得JQuery成为了几乎“人手必会”的前端类库。而受到JQuery的启发地ES委员会,创造性地在Javascript中引入了Promise对象。
Promise字面意义上代表承诺,在现实生活中,一个承诺也会有履行完成的状态和未履行的状态。因此,Promise对象也可以处理程序成功和失败后的数据。一个典型的Promise写法如下:
1 |
|
更好用的Promise: async / await
Promise除了擅长链式调用以外,还可以搭配关键字await写出更易读的同步式代码。
1 |
|