Promise构造函数接受一个函数作为参数吗?

Promise几乎是面试必考点,所以我们不能仅仅会用,还得知道他的底层原理,学习他原理的最好方法就是自己也实现一个Promise。所以本文会自己实现一个遵循Promise/A+规范的Promise。实现之后,我们还要用Promise/A+官方的测试工具来测试下我们的实现是否正确,这个工具总共有872个测试用例,全部通过才算是符合Promise/A+规范,下面是他们的链接:

本文的完整代码托管在GitHub上:

Promise的基本用法,网上有很多,我这里简单提一下,我还是用三个相互依赖的网络请求做例子,假如我们有三个网络请求,请求2必须依赖请求1的结果,请求3必须依赖请求2的结果,如果用回调的话会有三层,会陷入“回调地狱”,用Promise就清晰多了:

// 我们先用Promise包装下三个网络请求

上述代码输出如下图,符合我们的预期,说明到目前为止,我们的代码都没问题:

根据规范then的返回值必须是一个promise,规范还定义了不同情况应该怎么处理,我们先来处理几种比较简单的情况:

// 如果还是PENDING状态,也不能直接保存回调方法了,需要包一层来捕获错误
// 我们就根据要求加个判断,注意else里面是正常执行流程,需要resolve
 
 
    不是函数的时候已经做到了,因为我们默认给的onRejected里面会throw一个Error,所以代码肯定会走到catch里面去。但是我们为了更直观,代码还是跟规范一一对应吧。需要注意的是,如果promise1onRejected执行成功了,promise2应该被resolve。改造代码如下:
 
 
    x)。这条其实才是规范的第一条,因为他比较麻烦,所以我将它放到了最后。前面我们代码的实现,其实只要onRejected或者onFulfilled成功执行了,我们都要resolve promise2。多了这条,我们还需要对onRejected或者onFulfilled的返回值进行判断,如果有返回值就要进行 Promise 解决过程。我们专门写一个方法来进行Promise 解决过程。前面我们代码的实现,其实只要onRejected或者onFulfilled成功执行了,我们都要resolve promise2,这个过程我们也放到这个方法里面去吧,所以代码变为下面这样,其他地方类似:
 
 
 
现在我们该来实现resolvePromise方法了,规范中这一部分较长,我就直接把规范作为注释写在代码里面了。 // 这是为了防止死循环 // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y // 这个if跟下面判断then然后拿到执行其实重复了,可有可无 // 如果 x 为对象或者函数 // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve // 名字重名了,我直接用匿名函数了 // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 // 实现这条需要前面加一个变量called // 如果调用 then 方法抛出了异常 e:
 
在规范中还有一条:onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。所以在我们执行onFulfilledonRejected的时候都应该包到setTimeout里面去。
 
我们使用Promise/A+官方的测试工具来对我们的MyPromise进行测试,要使用这个工具我们必须实现一个静态方法deferred,官方对这个方法的定义如下:
 


在跑测试的时候发现一个坑,在resolvePromise的时候,如果x是null,他的类型也是object,是应该直接用x来resolve的,之前的代码会走到catch然后reject,所以需要检测下null
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
 
这个测试总共872用例,我们写的Promise完美通过了所有用例:




 
 
虽然这些都不在Promise/A+里面,但是我们也来实现一下吧,加深理解。其实我们前面实现了Promise/A+再来实现这些已经是小菜一碟了,因为这些API全部是前面的封装而已。
 
 
返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。
 
该方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
 

该方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
 
 
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
 
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由
 
完全版的代码较长,这里如果看不清楚的可以去我的GitHub上看:
// 先定义三个常量表示状态
 // 构造函数里面添加两个数组存储成功和失败的回调
 // resolve里面将所有成功的回调拿出来执行
 // resolve里面将所有失败的回调拿出来执行
 // 这是为了防止死循环
 // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
 // 这个if跟下面判断then然后拿到执行其实重复了,可有可无
 // 如果 x 为对象或者函数
 // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
 // 名字重名了,我直接用匿名函数了
 // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
 // 实现这条需要前面加一个变量called
 // 如果调用 then 方法抛出了异常 e:
 // 后面返回新promise的时候也做了onFulfilled的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
 // 后面返回新promise的时候也做了onRejected的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
 // 如果还是PENDING状态,将回调保存下来
 
 
至此,我们的Promise就简单实现了,只是我们不是原生代码,不能做成微任务,如果一定要做成微任务的话,只能用其他微任务API模拟,比如MutaionObserver或者process.nextTick。下面再回顾下几个要点:
  1. Promise其实是一个发布订阅模式
  2. Promise构造函数里面的resolve方法会将数组onFilfilledCallbacks里面的方法全部拿出来执行,这里面是之前then方法塞进去的成功回调
  3. 同理,Promise构造函数里面的reject方法会将数组onRejectedCallbacks里面的方法全部拿出来执行,这里面是之前then方法塞进去的失败回调
  4. then方法会返回一个新的Promise以便执行链式调用
  5. catchfinally这些实例方法都必须返回一个新的Promise实例以便实现链式调用
 
文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。
欢迎关注我的公众号第一时间获取高质量原创~
“前端进阶知识”系列文章源码地址:

我要回帖

更多关于 函数调用可以作为一个函数的实参 的文章

 

随机推荐