谈谈使用promise时候的一些反模式

513 查看

本文翻译自We have a problem with promises,同时也为原文题目,翻译时重新起了一个题目并且对原文有删改。

各位JavaScript程序员,是时候承认了,我们在使用promise的时候,会写出许多有问题的promise代码。 当然并不是promise本身的问题,A+ spec规范定义的promise非常棒。 在过去的几年中,笔者看到了很多程序员在调用PouchDB或者其他promise化的API时遇到了很多困难。这让笔者认识到,在JavaScript程序员之中,只有少数人是真正理解了promise规范的。如果这个事实让你难以接受,那么思考一下我给出的一个题目:

Question:下面四个使用promise的语句之间的不同点在哪儿?

如果你知道这个问题的答案,那么恭喜你,你已经是一个promise大师并且可以直接关闭这个网页了。

但是对于不能回答这个问题的程序员中99.9%的人,别担心,你们不是少数派。没有人能够在笔者的tweet上完全正确的回答这个问题,而且对于第三条语句的最终答案也令我感到震惊,即便我是出题人。

答案在这篇博文的底部,但是首先,笔者必须先介绍为何promise显得难以理解,为什么我们当中无论是新手或者是很接近专家水准的人都有被promise折磨的经历。同时,笔者也会给出自认为能够快速、准确理解promise的方法。而且笔者确信读过这篇文章之后,理解promise不会那么难了。

在此之前,我们先了解一下有关promise的一些基本设定。

promise从哪里来?

如果你读过有关promise的文章,你会发现文章中一定会提到回调深坑,不说别的,在视觉上,回调金字塔会让你的代码最终超过屏幕的宽度。

promise是能够解决这个问题的,但是它解决的问题不仅仅是缩进。在讨论到如何解决回调金字塔问题的时候,我们遇到真正的难题是回调函数剥夺了程序员使用return和throw的能力。而程序的执行流程的基础建立于一个函数在执行过程中调用另一个函数时产生的副作用。(译者注:个人对这里副作用的理解是,函数调用函数会产生函数调用栈,而回调函数是不运行在栈上的,因此不能使用return和throw)。

事实上,回调函数会做一些更邪恶的事情,它们剥夺我们在栈上执行代码的能力,而在其他语言当中,我们始终都能够在栈上执行代码。编写不在栈上运行的代码就像驾驶没有刹车的汽车一样,在你真正需要它之前,你是不会理解你有多需要它。

promise被设计为能够让我们重新使用那些编程语言的基本要素:return,throw,栈。在想要使用promise之前,我们首先要学会正确使用它。

新手常见错误

一些人尝试使用漫画的方式解释promise,或者是像是解释名词一样解释它:它表示同步代码中的值,并且能在代码中被传递。

笔者并没有觉得这些解释对理解promise有用。笔者自己的理解是:promise是关于代码结构和代码运行流程的。因此,笔者认为展示一些常见错误,并告诉大家如何修正它才是王道。

扯远一点,对于promise不同的人有不同的理解,为了本文的最终目的,我在这里只讨论promise的官方规范,在较新版本的浏览器会作为window对象的一个属性被暴露出来。然而并不是所有的浏览器都支持这一特性,但是到目前为止有许多对于规范的实现,比如这个有着很嚣张的名字的promise库:lie,同时它还非常精简。

新手错误No.1:回调金字塔

PouchDB有许多promise风格的API,程序员在写有关PouchDB的代码的时候,常常将promise用的一塌糊涂。下面给出一种很常见的糟糕写法。

你确实可以将promise当做回调函数来使用,但这却是一种杀鸡用牛刀的行为。不过这么做也是可行的。 你可能会认为这种错误是那些刚入行的新手才会犯的。但是笔者在黑莓的开发者博客上曾经看到类似的代码。过去的书写回调函数的习惯是很难改变的。

下面给出一种代码风格更好的实现:

这就是promise的链式调用,它体现promise的强大之处,每个函数在上一个promise的状态变为resolved的时候才会被调用,并且能够得到上一个promise的输出结果。稍后还有详细的解释。

新手错误2:怎样用forEach()处理promise

这个问题是大多数人掌握promise的拦路虎,当这些人想在代码中使用他们熟悉的forEach()方法或者是写一个for循环,亦或是while循环的时候,都会为如何使用promise而疑惑不已。他们会写下这样的代码: