未加星标

手写实现满足 Promise/A+ 规范的 Promise

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二03 | 时间 2018 | 作者 红领巾 ] 0人收藏点击收藏

最近看了Promise/A+ 的规范,尝试实现了一个满足 promises-aplus-tests 测试的 Promise 类,在实现规范的过程中,对于 Promise 本身也加深了理解,这篇文章就将我的实现过程分享出来。

本文的代码仓库在 这里 ,欢迎 Star~。 前置知识 Promise 是用来解决异步问题的一个方案,相当于异步操作的占位符。 每个 Promise 只有三种状态: pending , fulfilled 和 rejected ,状态只能从 pending 转移到 fulfilled 或者 rejected ,一旦状态变成 fulfilled 或者 rejected ,就不能再更改其状态。 2.1.1 When pending, a promise: 2.1.1.1 may transition to either the fulfilled or rejected state. 2.1.2 When fulfilled, a promise: 2.1.2.1 must not transition to any other state. 2.1.2.2 must have a value, which must not change. 2.1.3 When rejected, a promise: 2.1.3.1 must not transition to any other state. 2.1.3.2 must have a reason, which must not change. thenable 对象是一类具有 then 方法的对象或者函数。 1.2 “thenable” is an object or function that defines a then method. 每个 Promise 内部都有一个 value 值,这个 value 值可以是任意合法的 javascript 数据类型。 1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise). 除了 value 属性,Promise 内部还有一个 reason 属性,用来存放 Promise 状态变为 rejected 的原因 1.5 “reason” is a value that indicates why a promise was rejected. 构造 MyPromise 类

根据上面的介绍,可以初步构造一个 MyPromise 类:

class MyPromise { constructor(exector) { this.status = MyPromise.PENDING; this.value = null; this.reason = null; this.initBind(); this.init(exector); } initBind() { // 绑定 this // 因为 resolve 和 reject 会在 exector 作用域中执行,因此这里需要将 this 绑定到当前的实例 this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); } init(exector) { try { exector(this.resolve, this.reject); } catch (err) { this.reject(err); } } resolve(value) { if (this.status === MyPromise.PENDING) { this.status = MyPromise.FULFILLED; this.value = value; } } reject(reason) { if (this.status === MyPromise.PENDING) { this.status = MyPromise.REJECTED; this.reason = reason; } } } // 2.1 A promise must be in one of three states: pending, fulfilled, or rejected. MyPromise.PENDING = "pending" MyPromise.FULFILLED = "fulfilled" MyPromise.REJECTED = "rejected" 复制代码

exector 是创建 Promise 对象时传递给构造函数的参数, resolve 和 reject 方法分别用来将 Promise 对象的状态由 pending 转换成 fulfilled 和 rejected ,并向 Promise 对象中写入相应的 value 或者 reason 值。 现在,我们可以对上面的代码进行一些测试:

const p1 = new MyPromise((resolve,reject) => { const rand = Math.random(); if(rand > 0.5) resolve(rand) else reject(rand) }) console.log(p1) // MyPromise {status: "fulfilled", value: 0.9121690746412516, reason: null, resolve: , reject: } 复制代码

上面的代码,已经可以让 Promise 对象实现状态变换,并保存 value 或者 reason 值,但单纯完成状态的转换和保存值是不够的,作为异步的解决方案,我们还需要让 Promise 对象在状态变换后再做点什么。 这就需要我们为 Promise 对象再提供一个 then 方法。

A promise must provide a then method to access its current or eventual value or reason. then 方法

then 方法接受两个参数:Promise 状态转换为 fulfilled 的回调(成功回调)和状态转换为 rejected 的回调(失败回调),这两个回调函数是可选的。

A promise’s then method accepts two arguments: promise.then(onFulfilled, onRejected) 2.2.1 Both onFulfilled and onRejected are optional arguments: 2.2.1.1 If onFulfilled is not a function, it must be ignored. 2.2.1.2 If onRejected is not a function, it must be ignored.

下面为 MyPromise 类添加一个 then 方法:

... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {} onRejected = typeof onRejected === "function" ? onRejected : () => {} if (this.status === MyPromise.FULFILLED) { try{ onFulfilled(this.value) }catch(e){ onRejected(e) } } if (this.status === MyPromise.REJECTED) { try{ onRejected(this.reason); }catch(e){ onRejected(e) } } } ... 复制代码

下面测试一下 then 方法:

const p1 = new MyPromise((resolve) => resolve("Success")) p1.then(data => console.log(data)) // Success 复制代码

这里,我们初步完成了 MyPromise 类的 then 方法。 但仔细看上面的 then 方法和 MyPromise 类的实现,还存在几个缺陷:

只处理了状态为 fulfilled 和 rejected 的情况,没有处理状态为 pending 的情况 onFulfilled 和 onRejected 方法是同步执行的,也就是说,调用 then 方法,就会执行 onFulfilled 和 onRejected 方法 MyPromise 类中的 resolve 和 reject 方法也是同步的,这意味着会出现下面的情况: console.log("START") const p2 = new MyPromise(resolve => resolve("RESOLVED")) console.log(p2.value) console.log("END") 复制代码

输出结果为:

START RESOLVED END 复制代码

按照规范,Promise 应该是异步的。

2.2.4 onFulfilled or onRejected must not be called until theexecution contextstack contains only platform code.

规范还指出了,应该使用 setTimeout 或 setImmediate 这样的宏任务方式,或者 MutationObserver 或 process.nextTick 这样的微任务方式,来调用 onFulfilled 和 onRejected 方法。

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate , or with a “micro-task” mechanism such as MutationObserver or process.nextTick . Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

MyPromise 对象的状态不能被异步的改变,换句话说,无法满足 exector 方法为异步的情况: const p3 = new MyPromise(resolve => setTimeout(() => resolve("RESOLVED"))); p3.then(data => console.log(data)) // 无输出 复制代码

这里无输出的原因是在实现 then 方法的时候,没有处理状态为 pending 的情况,那么在 pending 状态下,对于 then 方法的调用,不会有任何的响应,因此在 then 方法中,对于 pending 状态的处理也很重要。 下面就针对上面出现的问题,做一些改进。

改进

首先,应该确保 onFulfilled 和 onRejected 方法,以及 resolve 和 reject 方法是异步调用的:

... resolve(value) { if (this.status === MyPromise.PENDING) { setTimeout(() => { this.status = MyPromise.FULFILLED; this.value = value; this.onFulfilledCallback.forEach(cb => cb(this.value)); }) } } reject(reason) { if (this.status === MyPromise.PENDING) { setTimeout(() => { this.status = MyPromise.REJECTED; this.reason = reason; this.onRejectedCallback.forEach(cb => cb(this.reason)); }) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

分页:12
转载请注明
本文标题:手写实现满足 Promise/A+ 规范的 Promise
本站链接:https://www.codesec.net/view/620881.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(10)