今天要写一个符合 a+ 规范的Promise。
a+规范地址(保姆级的开发规范):https://promisesaplus.com/
Promise 是基于回调实现的,通过回调函数队列来管理异步代码。这点与发布-订阅模式很相似。
Promise A+
术语
Promise A+ 规范有几个专门的术语,先来介绍下,以便后面的介绍。
- promise 是一个对象或者函数,拥有一个then方法,这个方法行为符合 A+ 规范。
- thenable 是一个对象或者函数,这个对象或函数定义了一个 then 方法。
- value 是任何合法的 JavaScript 值,包括undefined,thenable 和 promise。
- exception 异常,是用throw抛出的错误。
- reason 原因,是一个值表明 promise 为什么被拒绝。
必备项
Promise的状态
promise 有三种状态:pending(初始态/等待态)、fulfilled(成功态/完成态)、rejected(失败态/拒绝态)。promise必定是这三个状态之一。有以下几个特点:
- 当 promise 是初始态时,状态可以转变为成功态或者失败态。
- 当 promise 是完成态时,状态不能改变为其他状态,并且必定有一个值 value(返回之后不可改变)。
当 promise 是失败态时,状态不能改变为其他状态,并且必定有一个原因 reason(返回之后不可改变)。
function Promise() {this.status = 'pending'; // pending / fulfilled / rejected}
then 方法
promise 必定会提供一个 then 方法获取它当前的或者最终的值或原因。then 方法接受两个函数作为参数 onFulfilled 和 onRejected,使用如下:
promise.then(onFulfilled, onRejected);
onFulfilled 和 onRejected 都是可选的。
- 如果 onFulfilled 不是一个函数,就要被忽略。
- 如果 onRejected 不是一个函数,就要被忽略。
- 如果 onFulfilled 是一个函数。
- 它一定是 promise 在成功之后被调用,并且以 promise 的值 value 作为第一个参数。
- 它不能在成功之前被调用。
- 它不能被调用多次。
- 如果 onRejected 是一个函数。
- 它一定是 promise 在失败之后被调用,并且以 promise 的原因 reason 作为第一个参数。
- 它不能在失败之前被调用。
- 它不能被调用多次。
- onFulfilled 和 onRejected 必须直到执行上下文栈仅包含平台代码才能调用。
- onFulfilled 和 onRejected 必须作为函数被调用。
- then 方法可以被调用多次,在同一个promise内。
- 当 promise 成功之后,所有的各自的 onFulfilled 回调函数都将按照他们起源的then方法的顺序被执行被执行。
- 当 promise 失败之后,所有的各自的 onRejected 回调函数都将按照他们起源的then方法的顺序被执行被执行。
then 方法会返回一个新的 promise。
promise2 = promise1.then(onFulfilled, onRejected);
- 无论是 onFulfilled 还是 onRejected 返回了一个值 x,都要运行 promise 解决方案。
- 无论是 onFulfilled 还是 onRejected 抛出了错误异常,promise2 必须被拒绝并且以 e 作为参数。
- 如果 onFulfilled 不是一个函数,并且 promise1 已经成功,promise2 必须成功并且返回相同的值。
- 如果 onRejected 不是一个函数,并且 promise1 已经失败,promise2 必须失败并且返回相同的原因。
The Promise Resolution Procedure
Promise 的解决程序是一个抽象操作,取得输入的 promise 和值 x(结果)。
如果 promise 和 x 引用的同一个对象,就抛出一个 TypeError。
- 如果 x 是个 promise,则采用它的状态。
- 如果 x 是等待态,promise 保持 pending 直到它被解决或者拒绝。
- 如果 x 被解决,采用相同的值 value。
- 如果 x 被拒绝,采用相同的原因 reason。
- 否则 x 是个对象或者方法
- let then = x.then;
- 如果取到的属性 x.then 导致抛出异常,拒绝 promise 并把抛出的错误 e 作为原因。
- 如果 then 是一个方法,将 then 的 this 绑定为 x,第一个参数为解决函数 resolvePromise,第二个参数为拒绝函数 rejectPromise。
- 当 resolvePromise 被调用时,会被传入值 y,并且递归调用 Promise 解决程序。
- 当 rejectPromise 被调用时,会被传入原因 r,然后以 r 为参数拒绝 promise2。
- 如果 resolvePromise 和 rejectPromise 都被调用,或者对同一个参数进行多次调用,则第一次调用优先,并且任何进一步的调用都会被拒绝。
- 如果 then 抛出异常 e
- 如果 resolvePromise 和 rejectPromise 已经被调用则忽略它
- 否则以 e 为原因拒绝 promise2
- 如果 then 不是一个函数,以 x 为参数解决promise
- 如果 x 不是一个对象或者函数,以 x 为参数解决promise
实现主干代码
构造函数
首先,我们要明确 Promise 是通过 new 声明的,所以必然是个构造函数,并且接受一个函数(executor)作为参数。executor 函数提供两个内部函数 resolve 和 reject 作为参数:
- resolve 当成功时调用,接收一个 value 作为成功的值
reject 当失败时调用,接受一个 reason 作为失败的原因
// Promise 构造函数function Promise(executor) {function resolve(value) { //变成成功态//...}function reject(reason) { //变成失败态//...}executor(resolve, reject);//立即执行}
下面添加一些变量用于保存内部数据:
state 用于保存 promise的三个状态:pending、fulfilled、rejected。
- value 保存成功返回的值。
reason 保存失败返回的原因。
function Promise(executor) {this.status = 'pending'; // pending / fulfilled / rejectedthis.value = undefined;this.reason = undefined;function resolve(value) { //变成成功态//...}function reject(reason) { //变成失败态//...}executor(resolve, reject);//立即执行}
有了状态、值、原因之后,完善一下 resolve 和 reject,在前文的 A+ 规范中写明了,只有 pending状态的promsie才能改变状态,在其他状态下无法变更为其他状态,所以代码如下:
//...let self = this; //由于 resolve 和 reject 是window调用的,所以这边保存下thisfunction resolve(value) { //变成成功态if (self.status === 'pending') {self.value = value;self.status = 'fulfilled';}}function reject(reason) { //变成失败态if (self.status === 'pending') {self.reason = reason;self.status = 'rejected'}}//...
到目前为止 resolve 和 reject 还缺少一些功能,即promise完成或者失败后,需要执行 then 方法中的回调函数,promise 是基于回调函数来实现的,其实就是发布订阅,所以需要通过中间数组来保存回调函数,这边onFulfilled和 onRejected 各需要一个,并且需要再改变状态后执行,如下: ```javascript
function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;
this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let self = this;function resolve(value) { //变成成功态if (self.status === 'pending') {self.value = value;self.status = 'fulfilled';self.onFulfilledCallbacks.forEach(fn => fn());}}function reject(reason) { //变成失败态if (self.status === 'pending') {self.reason = reason;self.status = 'rejected';self.onRejectedCallbacks.forEach(fn => fn());}}executor(resolve, reject);//立即执行
}
<a name="WSs0I"></a>### then 方法<a name="dUvPr"></a>#### 入队如何执行回调队列已经实现了,接下来就是通过 then 方法将成功回调和失败回调放入回调队列中。- 当 promise 状态为 pending 时,将回调函数放入回调队列中。- 当 promise 状态为 fulfilled 或者 rejected 时,立即执行。如下:```javascriptPromise.prototype.then = function (onFulfilled, onRejected) {let self = this;if (self.status === 'fulfilled') {onFulfilled(self.value);}if (self.status === 'rejected') {onRejected(self.reason);}if (self.status === 'pending') {self.onFulfilledCallbacks.push(function () {onFulfilled(self.value)})self.onRejectedCallbacks.push(function () {onRejected(self.reason)})}}
实现链式调用
每一个 promise 的状态在改变之后就无法改变了,所以每个 then 方法必须返回一个新的 promise,保证链式调用。
function resolvePromise(promise2, x, resolve, reject){//... promise 解决程序}Promise.prototype.then = function (onFulfilled, onRejected) {let self = this;let promise2 = new Promise(function(resolve, reject){if (self.status === 'fulfilled') {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);}if (self.status === 'rejected') {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);}if (self.status === 'pending') {self.onFulfilledCallbacks.push(function () {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);})self.onRejectedCallbacks.push(function () {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);})}})return promise2;}
这边引用了一个 resolvePromise 方法,主要是对返回值的处理,详情见 A+ 规范。同时这边用 setTimeout 包裹起来,原因是在 promise2 实例化的过程中,调用 promise2 是undefined,使用宏任务可以使 执行完主栈代码之后再调用 resolvePromise 方法。
参数的可选性
Promise.prototype.then = function (onFulfilled, onRejected) {//参数的可选性onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };//...}
错误处理
Promise.prototype.then = function (onFulfilled, onRejected) {//参数的可选性onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };let self = this;let promise2 = new Promise(function (resolve, reject) {if (self.status === 'fulfilled') {setTimeout(() => {try {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})}if (self.status === 'rejected') {setTimeout(() => {try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})}if (self.status === 'pending') {self.onFulfilledCallbacks.push(function () {setTimeout(() => {try {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})})self.onRejectedCallbacks.push(function () {setTimeout(() => {try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})})}})return promise2;}
The Promise Resolution Procedure(resolvePromise 函数)
- 判断 promise2 和 x 是否指向同一对象,是则抛出异常 循环引用。
判断是否是promise,由于要兼容其他人的Promise,所以采用
//满足条件则认为是 promiseif(x !== null && (typeof x === 'object' || typeof x === 'function')){//...}
需要判断 resolve和reject是否已经被调用,调用过后忽略后面的调用。
// 这个方法要兼容别人的promise 严谨一些function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个throw new TypeError('循环引用');}if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promiselet called;try {let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}if (typeof then === 'function') {then.call(x, y => {if (called) return;called = true;//如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止resolvePromise(promise2, y, resolve, reject);}, r => {if (called) return; //防止调用成功后又调用失败called = true;reject(r);})} else {resolve(x);// {then:{}} {then:123}}} catch (e) { //这个then方法是通过 Object.defineProperty 定义的if (called) return;called = true; //这个判断为了防止出错后 继续调用成功逻辑reject(e);}} else {resolve(x); // x 就是普通对象}}
完善一下整体的错误处理
```javascript function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;
this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; let self = this;
function resolve(value) { //变成成功态
if (value instanceof Promise) {return value.then(resolve, reject)}if (self.status === 'pending') {self.value = value;self.status = 'fulfilled';self.onFulfilledCallbacks.forEach(fn => fn());}
}
function reject(reason) { //变成失败态
if (self.status === 'pending') {self.reason = reason;self.status = 'rejected';self.onRejectedCallbacks.forEach(fn => fn());}
} try {
executor(resolve, reject);//立即执行
} catch (e) {
reject(e)
} }
// 这个方法要兼容别人的promise 严谨一些 function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个 throw new TypeError(‘循环引用’); }
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promiselet called; //有的人 promise 既可以调成功 也可以调失败,所以这边兼容下,防止他重复调用try {let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}if (typeof then === 'function') {then.call(x, y => {if (called) return;called = true;//如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止resolvePromise(promise2, y, resolve, reject);}, r => {if (called) return; //防止调用成功后又调用失败called = true;reject(r);})} else {resolve(x);// {then:{}} {then:123}}} catch (e) { //这个then方法是通过 Object.defineProperty 定义的if (called) return;called = true; //这个判断为了防止出错后 继续调用成功逻辑( 这里取 then 的时候可能会报错,但是报错之后可能继续往下走)reject(e);}} else {resolve(x); // x 就是普通对象}
}
Promise.prototype.then = function (onFulfilled, onRejected) { //参数的可选性 onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : val => val; onRejected = typeof onRejected === ‘function’ ? onRejected : err => { throw err };
let self = this;let promise2 = new Promise(function (resolve, reject) {if (self.status === 'fulfilled') {setTimeout(() => {try {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})}if (self.status === 'rejected') {setTimeout(() => {try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})}if (self.status === 'pending') {self.onFulfilledCallbacks.push(function () {setTimeout(() => {try {let x = onFulfilled(self.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})})self.onRejectedCallbacks.push(function () {setTimeout(() => {try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}})})}})return promise2;
}
<a name="JUTfh"></a>##<a name="Dm00q"></a>## 延迟对象<a name="tVOAj"></a>### 实现```javascriptPromise.deferred = function(){let dtd = {};dtd.promise = new Promise((resolve, reject)=>{dtd.resolve = resolve;dtd.reject = reject;})return dtd;}
使用
let fs = require('fs');let Promise = require('./promise');function read(url) {// 延迟对象let defer = Promise.deferred(); //{promise, resolve, reject}fs.readFile(url, 'utf8', function (err, data) {if (err) return defer.reject(err);defer.resolve(data);})return defer.promise;}read('./promise/name.txt').then(data=>{console.log(data)},err=>{console.log(err)})
A+ 规范测试程序
到此为止,实现了主干代码,并且配置好 延迟对象后,可以执行单元测试对程序进行验证。配置如下:
- 安装包 npm install promises-aplus-tests
- 运行命令 npx promises-aplus-tests 路径
得到结果 872 个测试程序运行通过,即符合A+规范
其他原型方法
Promse.prototype.catch()
Promise.prototype.catch = function(errorCallback){return this.then(null, errorCallback);}
Promise.prototype.finally()
Promise.prototype.finally = function (callback) {return this.then(value => {return Promise.resolve(callback()).then(()=>{return value;})}, reason => {return Promise.resolve(callback()).then(()=>{throw reason;})})}
静态方法
Promise.resolve()
Promise.resolve = function(value){return new Promise((resolve, reject)=>{resolve(value);})}
Promise.reject()
Promise.reject = function(reason){return new Promise((resolve, reject)=>{reject(reason);})}
Promise.all()
Promise.all = function (values) {return new Promise((resolve, reject) => {let arr = [];let count = 0;function processData(key, value) {arr[key] = value;if (++count === values.length) {resolve(arr)}}for (let i = 0; i < values.length; i++) {let current = values[i];let then = current.then;if (then && typeof then === 'function') {then.call(current, y => {processData(i, y);}, reject)} else {processData(i, values[i])}}})}
Promise.race()
Promise.race = function (values) {return new Promise(function(resolve, reject) {for (let i = 0; i < values.length; i++) {let current = values[i];let then = current.then;if (then && typeof then === 'function') {then.call(current, y => {resolve(y)}, reject)} else {resolve(current);break;}}})}
Node 方法 Promise 化
//node中的所有方法 都是错误优先 第二个就是结果 bluebirdfunction promisify(fn) {// 把方法promise话return function () {return new Promise((resolve, reject) => {//fn.call(null, ...args)fn(...arguments, function (err, data) {if (err) reject(err);resolve(data);})})}}function promisifyAll(obj) {for (let key in obj) {//遍历整个对象 如果是函数的 我就把方法重写if(typeof obj[key] === 'function'){obj[key + 'Async'] = promisify(obj[key]); //把每个方法都promise化}}}
