相关文章推荐
Skip to content

深入理解promise

一直以来,对promise也只是有所耳闻,但是并未有过深入的学习和理解。昨天看到了 这篇文章 ,又想到ES6中都提供原生Promise了,因此很有必要深入理解下。

Q中的promise设计

本部分内容主要编译自 https://github.com/kriskowal/q/tree/v1/design

假设你写了一个无法立即返回的函数,那么显而易见的做法是将最终的返回值作为回调函数的参数传入。例如:

通常情况下,该函数可能执行成功,也可能失败,因此需要对这两种情况进行分别处理,此时可以使用两个回调函数: callback errback 。例如:

我们可以采取更加普遍的做法,我们让这个函数返回一个对象,该对象用来表示函数的执行结果,那么这个对象可能表示函数执行成功,也可能表示函数执行失败。在这种情况下,返回的对象就是一个promise,我们通过调用该对象上的方法来对实际的结果进行观察。例如:

在这个例子中,存在两个问题:

  • 在调用 then 的时候,就确定了回调函数。因此假如我们希望有多个回调函数,就无能为力了。
  • 假如 then 的执行在一秒钟之后,那么就会出错。因为此时当异步函数结果返回的时候,回调函数还没有注册,例如:

因此,更加通用的解决方案是:可以注册任意数量的回调函数,并且回调函数的注册可以在异步任务结果返回前或者返回后。例如:

在这个例子中,promise有了两个状态,完成( resolved )和未完成( pending )。通过 pending 这个变量来表示promise的状态,如果未完成,那么就将新的回调函数加入到回调函数列表(即 pending )中,一旦状态为完成,也就是回调函数结果返回,那么依次执行回调函数,并且将 pending 设置为 undefined ,如果在这之后还有新的回调函数,由于已经完成,因此新的回调函数将会立即执行。

基于上面的理念,可以抽象出一个 defer 对象。一个 defer 对象由两部分组成:注册回调( then )和通知回调( resolve )。例如:

但是这样子还有一个小小的问题,即 resolve 方法可以被多次调用,而这并不是我们所希望的。为了解决这个问题,代码修改如下:

这里采用的是报错的方式,其实也可以不做任何处理,即忽略后面的多次 resolve 。到此为止,promise的功能以及基本完成,然而还存在着一些小问题,例如:

在这个例子中,由于用户在外面执行了 resolve ,因此真正的返回结果(即 result.resolve(1); )将会被忽略,这显然不是我们所期望的。也就是说,在上面的设计中,promise的权限还是太大,理想的情况下,promise只能执行 then ,而不能够执行 resolve 。为了达到这个目的,代码修改如下:

然而这样子依然有着小问题。即在判断一个值是不是promise的时候( isPromise ),假如有多个promise类,就会导致判断错误。因此,更好的做法是使用 duck-typing ,即在判断一个值是不是promise的时候,我们通过检测它有没有特定的方法(依惯例,这里使用的是 then )。虽然可能一个非promise对象恰巧也有 then 方法,但是这种情况在实际中并不常见且不会有什么影响。修改后代码如下:

上面都是针对一个promise的情况,下面考虑有多个异步任务的情况。假如现在需要执行两个异步任务得到两个数字,然后计算它们的和,通过回调函数的方式代码如下:

但是这种做法并不十分友好,我们所期望的可能是如下这样的:

要达到以上效果,需要满足以下条件:

  • then 方法返回一个promise对象
  • 返回的promise对象最终必须能够使用回调函数的返回值 resolve
  • 回调函数的返回值要么是一个最终的结果要么是一个promise对象

下面的方法是将一个变量值转为promise对象:

然而当 value 本身就是一个promise对象的时候是不需要转换的,因此:

现在需要将 then 方法的返回值转变为一个promise对象,如下:

此时的defer代码如下:

这段代码比较复杂,要理解它,就需要明白:

  • resolve 方法是通知回调函数执行,并将异步任务的结果作为回调函数的参数传入。
  • then 方法的作用是添加回调(未resolve),或者添加回调并立即执行(已经resolve)
  • 为了 then 方法可以链式调用, then 方法需要返回一个promise对象

ES6中的Promise

在ES6中,提供了原生的Promise类,其基本用法如下:

例如:

 
推荐文章