下列代码是一个简单的Promise的使用案例,我们基于这个案例实现一个自己的Promise。

let p = new Promise(funcion(resolve, reject) {
   reject('失败啦');
   resolve('成功啦');
});
p.then(function(value) {
   console.log(value);
})

这里我们自己去简单实现一个Promise,我们先定义一个Promise的构造函数。
我们在创建Promise对象的时候会传递一个函数executor,这个函数会立即被调用,所以我们在Promise内部立即执行这个函数。

function Promise (executor) {
   excutor();
}

executor在执行的时候会传入两个方法,一个是resolve,一个reject,所以我们要创建这两个函数,而且需要把这两个函数传递给executor。
我们在调用resolve和reject的时候会传入一个信息,成功的时候传递一个成功的值,失败的时候传递一个失败的原因。

function Promise (executor) {
   function resolve(value) {
   }
   function reject(reason) {
   }
   excutor(resolve, reject);
}

Promise的对象存在一个then方法,这个then方法里面会有两个参数,一个是成功的回调onFulfilled,另一个是失败的回调onRejected,只要我们调用了resolve就会执行onFulfilled,调用了reject就会执行onRejected。

Primsie.prototype.then = function(onFulfilled, onRejected) {
}

Promise本身是存在状态的,默认是等待状态pending,调用了resolve会变成成功状态,调用了reject会变为失败状态,所以我们需要在Promise内部定义一个状态表示当前Promise的状态。
为了保证this不错乱,我们定义一个self存储this。当我们调用了resolve或reject的时候,需要让状态发生改变,Promise中的状态只可改变一次,所以我们要判断,只有当状态未发生改变时,才去改变状态。
调用成功resolve或失败reject的时候传递的值我们需要存储下来,在then中的方法中传入。

function Promise (executor) {
   var self = this;
   self.status = 'pending';
   self.value;
   self.reason;
   function resolve(value) {
       if (self.status === 'pending') {
           self.status = 'resolved';
           self.value = value;
       }

   }
   function reject(reason) {
       if (self.status === 'pending') {
           self.status = 'rejected';
           self.reason = reason;
       }
   }
   excutor(resolve, reject);
}

接着我们在then方法中判断,当状态成功的时候执行onFulfilled,当状态失败的时候执行onRejected。并且传入相应的值。

Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   if (self.status === 'resolved') {
       onFulfilled(self.value);
   }
   if (self.status === 'rejected') {
       onRejected(self.reason);
   }
}

至此我们就实现了一个简单的Promise代码。Promise的概念不是凭空出现的,是在Promise A+规范(https://promisesaplus.com/)中定义的,这个规范是所有人想实现Promise都必须要基于这个规范。
我们自己写的Promise可能会和别人的Promise用在一起,为了保证兼容性,所有人写的Promise都要符合这个规范。

规范

1.1 “promise”是一个具有then方法的对象或函数,其行为符合此规范。
1.2 “thenable”是一个定义then方法的对象或函数,也就是这个对象要拥有then方法。
1.3 “value”是任何合法的JavaScript值(包括undefined、或者promise)。
1.4 promise中的异常需要使用throw语句抛出。
1.5 当promise失败的时候需要给出失败的原因。

状态

1.1 promise必须要拥有三个状态: pending, fulfilled 和 rejected。
1.2 当promise的状态是pending时,他可以变为成功fulfilled或者失败rejected。
1.3 如果promise是成功状态,则他不能转换成任何状态,而且需要一个成功的值,并且这个值不能被改变。
1.4 如果promise是失败状态,则他不能转换成任何状态,而且需要一个失败的原因,并且这个值不能被改变。

then方法说明

1.1 一个promise必须要有一个then方法,而且可以访问promise最终的结果,成功或者失败的值。
1.2 then方法需要接收两个参数,onFulfilled 和 onRejected这两个参数是可选参数。
以上我们的代码是符合这些规范的,我们继续向下写。
现在我们的promise是实现了第一版最简易的功能,但是他存在一个异步逻辑的问题,当我们在Promise函数中异步调用resolve的时候,then方法不会执行。因为then方法执行的时候resolve并没有执行,也就是promise的状态还未变化。
同一个实例绑定多个then方法,所有的then绑定的成功或失败都会相应的执行。

let p = new Promise(funcion(resolve, reject) {
   setTimeout(function() {
       resolve('成功啦');
   }, 1000)    
});
p.then(function(value) {
   console.log(value);
})
p.then(function(value) {
   console.log(value);
})
p.then(function(value) {
   console.log(value);
})

这里我们就需要改造我们的Promise代码。当我们调用then方法的时候可能还是pending状态,这个时候我们应该把onFulfilled和onRejected先存起来,当执行了resolve或者reject的时候再执行onFulfilled或onRejected。
所以我们需要定义两个变量,分别存储onFulfilled和onRejected。

function Promise (executor) {
   var self = this;
   self.status = 'pending';
   self.value;
   self.reason;
   self.onResolvedCallbacks = []; // 存放所有成功的回调。
   self.onRejectedCallbacks = []; // 存放所有失败的回调。
   function resolve(value) {
       if (self.status === 'pending') {
           self.status = 'resolved';
           self.value = value;
       }

   }
   function reject(reason) {
       if (self.status === 'pending') {
           self.status = 'rejected';
           self.reason = reason;
       }
   }
   excutor(resolve, reject);
}
Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   if (self.status === 'resolved') {
       onFulfilled(self.value);
   }
   if (self.status === 'rejected') {
       onRejected(self.reason);
   }
   if (self.status === 'pending') {
       self.onResolvedCallbacks.push(onFulfilled);
       self.onRejectedCallbacks.push(onRejected);
   }
}

当我们成功或者失败的时候,执行onFulfilled和onRejected的函数,也就是在resolve函数中和reject函数中分别循环执行对应的方法数组。这相当数一个发布订阅模式。

function resolve(value) {
   if (self.status === 'pending') {
       self.status = 'resolved';
       self.value = value;
       self.onResolvedCallbacks.forEach(function (fn) {
           fn();
       })
   }
}
function reject(reason) {
   if (self.status === 'pending') {
       self.status = 'rejected';
       self.reason = reason;
       self.onRejectedCallbacks.forEach(function (fn) {
           fn();
       })
   }
}

这个时候当我们异步执行resolve方法时候,then中绑定的函数就会执行,并且绑定多个then的时候,多个方法都会执行。

链式调用

Promise最大的优点就是链式调用,如果一个then方法返回一个普通值,这个值会传递给下一次then中,作为成功的结果。如果返回的是一个primise, 则会把promise的执行结果传递下去取决于这个promise的成功或失败。如果返回的是一个报错就会执行到下一个then的失败的函数中。
捕获错误的机制是,默认会找距离自己最近的then的失败方法,如果找不到就向下继续找,一直找到catch方法。

let p = new Promise(funcion(resolve, reject) {
   setTimeout(function() {
       resolve('成功啦');
   }, 1000)    
});
p.then(function(value) {
   retrun 123;
}).then(function(value) {
   console.log(value);
}).catch(function(error) {
   console.log(error);
}).then(function(data) {
   console.log(data);
})

Promise调用then后会返回一个新的Promise,因为Promise的状态只能改变一次,如果使用同一个Promise的话后面的then就失去了成功失败的自由性。
所以我们需要在then方法之后再去return一个new Promise, 原本的逻辑放在新创建的Promise内部即可,因为他是立即执行的一个函数。我们这里定义一个promise2接收新创建的Promise,在函数底部返回出去。

Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   const promise2 = new Promise(function (resolve, reject) {
       if (self.status === 'resolved') {
           onFulfilled(self.value);
       }
       if (self.status === 'rejected') {
           onRejected(self.reason);
       }
       if (self.status === 'pending') {
           self.onResolvedCallbacks.push(function () {
               onFulfilled(self.value);
           });
           self.onRejectedCallbacks.push(function() {
               onRejected(self.reason);
           });
       }
   })
   return promise2;
}

我们需要拿到当前then方法执行成功或失败的结果,前一个then方法的返回值会传递给下一个then方法,所以这里我们要关心onFulfilled(self.value) 和 onRejected(self.reason)的返回值,我们这里定义一个x来接收一下。

...
const x = onFulfilled(self.value);
...
const x = onRejected(self.reason);
...

如果x是一个普通值的话,我们可以直接调用promise2的resolve方法,将这个值传递出去即可,这样下一个then就可以获取的到,所以我们执行resolve(x)即可。

Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   const promise2 = new Promise(function (resolve, reject) {
       if (self.status === 'resolved') {
           const x = onFulfilled(self.value);
           resolve(x);
       }
       if (self.status === 'rejected') {
           const x = onRejected(self.reason);
           resolve(x);
       }
       if (self.status === 'pending') {
           self.onResolvedCallbacks.push(function () {
               const x = onFulfilled(self.value);
               resolve(x);
           });
           self.onRejectedCallbacks.push(function() {
               const x = onRejected(self.reason);
               resolve(x);
           });
       }
   })
   return promise2;
}

如果失败抛错需要执行reject方法,这里使用try…catch捕获一下错误。也就是判断then函数的执行结果和promise2的关系。

Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   const promise2 = new Promise(function (resolve, reject) {
       if (self.status === 'resolved') {
           try {
               const x = onFulfilled(self.value);
               resolve(x);
           } catch(e) {
               reject(e);
           } 
       }
       if (self.status === 'rejected') {
           try {
               const x = onRejected(self.reason);
               resolve(x);
           } catch(e) {
               reject(e);
           } 
       }
       if (self.status === 'pending') {
           self.onResolvedCallbacks.push(function () {
               try {
                   const x = onFulfilled(self.value);
                   resolve(x);
               } catch(e) {
                   reject(e);
               } 
           });
           self.onRejectedCallbacks.push(function() {
               try {
                   const x = onRejected(self.reason);
                   resolve(x);
               } catch(e) {
                   reject(e);
               } 
           });
       }
   })
   return promise2;
}

我们知道onFulfilled(self.value)返回的值不一定是一个常量,还可能是个promise,我们这里写一个方法来判断下,如果返回值是promise就调用promise,如果不是promise才继续向resolve传递。
我们这里定义一个resolvePromise方法,来解析promise,在这个函数中我们要判断返回值x和promse2的关系,所以我们需要传递promise2参数,x参数,resolve参数和reject参数。
promise2参数,x参数,resolve参数和reject参数不能直接传递至resolvePromise中,因为文档要求他们不能在当前的上下文中执行,所以我们要在整个代码块外层添加setTimeout, 在异步线程中添加。

function resolvePromise (promise2, x, resolve, reject) {
}
Primsie.prototype.then = function(onFulfilled, onRejected) {
   var self = this;
   const promise2 = new Promise(function (resolve, reject) {
       if (self.status === 'resolved') {
           setTimeout(function() {
               try {
                   const x = onFulfilled(self.value);
                   resolvePromise(promise2, x, resolve, reject);
               } catch(e) {
                   reject(e);
               } 
           }, 0)
       }
       if (self.status === 'rejected') {
           setTimeout(function() {
               try {
                   const x = onRejected(self.reason);
                   resolvePromise(promise2, x, resolve, reject);
               } catch(e) {
                   reject(e);
               }
           }, 0)
       }
       if (self.status === 'pending') {
           self.onResolvedCallbacks.push(function () {
               setTimeout(function() {
                   try {
                       const x = onFulfilled(self.value);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch(e) {
                       reject(e);
                   }
               }, 0)
           });
           self.onRejectedCallbacks.push(function() {
               setTimeout(function() {
                   try {
                       const x = onRejected(self.reason);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch(e) {
                       reject(e);
                   }
               }, 0)
           });
       }
   })
   return promise2;
}

resolvePromise这个函数的作用就是判断x是不是promise,如果是promise就执行他,然后将它的结果调用resolve方法,入如果x是常量,直接调用resove方法。这些内容在文档上都可以找得到,具体可以自行翻阅文档,这里就不列出了,直接代码实现。
如果promise2和x引用了一个相同的对象,也就是他们是同一个promise对象,应该抛出一个类型错误作为错误原因。

const p = new Promise(function(resolve, reject) {
   resolve('成功');
})
const promise2 = p.then(data => { // 这个时候x和promise2就是相等的,也就是自己等待自己去做做完什么事,等 和 做某事不能同时执行。
   return promise2;
})

resolvePromise中的代码实现如下:

function resolvePromise (promise2, x, resolve, reject) {
   if (promise2 === x) { // 防止自己等待自己
       return reject(new TypeError('循环引用了'));
   }
}

如果x是对象或者是一个函数,我们就取他的then方法,但是获取then方法的时候如果出现异常,就执行失败。因为then方法可能是对象的一个不可访问的方法,get的时候报异常,所以我们需要使用try…catch去获取。
如果x不是一个promise,是个普通值,直接调用resolve就可以。

function resolvePromise (promise2, x, resolve, reject) {
   if (promise2 === x) { // 防止自己等待自己
       return reject(new TypeError('循环引用了'));
   }
   // x是object或者是个function
   if ((x !== null && typeof x === 'object') || typeof x === 'function') {
       try {
           let then = x.then;
       } catch (e) {
           reject(e);
       }
   } else {
       resolve(x);
   }
}

如果then是一个函数,就认为他是Promise, 需要使用call执行then方法,改变this的指向为x, then中传入成功和失败的函数, 官方文档中指明成功函数的参数叫y,失败的参数为r。
如果then不是一个Promise那么当前这个then是一个普通对象,调用resolve方法直接返回即可。

try {
   let then = x.then;
   if (typeof then === 'function') {
       then.call(x, function (y) {
           resolve(y); // 成功的结果,让promise2变为成功状态
       }, function (r) {
           reject(r);
       });
   } else {
       resolve(x)
   }
} catch (e) {
   reject(e);
}

这里面的y有可能也是一个Promise,所以我们这里不能直接写resolve(y),应该递归判断y和promise2的关系。所以我们这里要调用resolvePromise。y是then的返回值,和之前的x基本一个概念。
为什么这里要用递归呢,因为then返回的可能是一个Promise嵌套,也就是Promise中仍旧包含Promise,在Promise的标准这,这样的写法是被允许的。所以要用递归来解决,拿到最终的返回,也就是基本类型。

try {
   let then = x.then;
   if (typeof then === 'function') {
       then.call(x, function (y) {
           resolvePromise (promise2, y, resolve, reject)
           // resolve(y); // 成功的结果,让promise2变为成功状态
       }, function (r) {
           reject(r);
       });
   } else {
       resolve(x)
   }
} catch (e) {
   reject(e);
}

我们的Promise可能会和别人的Promise嵌套使用,官方文档要求,Promise中要书写判断,避免对方Promise不规范产生的影响。
比如对方的Promise成功和失败都调用了,或者多次调用了成功。需要使用一个called的变量来表示Promise有没有被调用过。
一旦状态改变就不能再改变了。

function resolvePromise (promise2, x, resolve, reject) {
   if (promise2 === x) { // 防止自己等待自己
       return reject(new TypeError('循环引用了'));
   }
   let called; // 表示Promise有没有被调用过
   // x是object或者是个function
   if ((x !== null && typeof x === 'object') || typeof x === 'function') {
       try {
           let then = x.then;
           if (typeof then === 'function') {
               then.call(x, function (y) {
                   if (called) { // 是否调用过
                       return;
                   }
                   called = true;
                   resolvePromise (promise2, y, resolve, reject)
               }, function (r) {
                   if (called) { // 是否调用过
                       return;
                   }
                   called = true;
                   reject(r);
               });
           } else { // 当前then是一个普通对象。
               resolve(x)
           }
       } catch (e) {
           if (called) { // 是否调用过
               return;
           }
           called = true;
           reject(e);
       }
   } else {
       if (called) { // 是否调用过
           return;
       }
       called = true;
       resolve(x);
   }
}

我们的Promise还存在一个小问题,如果我们的Promise有多个then方法,只在最后一个then方法中传递了onFulfilled, 是需要将Promise的返回值传递过去的,也就是下面的代码需要用内容输出。这叫值的穿透。

p.then().then().then(function(data) {
   console.log(data);
})

实现起来也比较简单,假如用户没有传递onFulfilled,或者传入的不是函数,我们可以给个默认值,也就是这个参数是一个可选参数。
onRejected的话逻辑也是一样,只是最后需要将错误抛出,传递给下一个reject,如果返回的话会流入下一个resolve。

Primsie.prototype.then = function(onFulfilled, onRejected) {
   onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
   onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
}

最后我们还存在一个小问题,我们在调用executor的时候,可能也会出错,只要Promise出现错误,就需要走到then的reject中,所以我们需要try…catch一下executor。

try {
   executor(resove, reject);
} catch (e) {
   reject(e);
}

至此,Promise我们就写完了,全部代码如下:

function Promise (executor) {
   var self = this;
   self.status = 'pending';
   self.value;
   self.reason;
   self.onResolvedCallbacks = []; // 存放所有成功的回调。
   self.onRejectedCallbacks = []; // 存放所有失败的回调。
   function resolve(value) {
       if (self.status === 'pending') {
           self.status = 'resolved';
           self.value = value;
           self.onResolvedCallbacks.forEach(function (fn) {
               fn();
           })
       }
   }
   function reject(reason) {
       if (self.status === 'pending') {
           self.status = 'rejected';
           self.reason = reason;
           self.onRejectedCallbacks.forEach(function (fn) {
               fn();
           })
       }
   }
   try {
       executor(resove, reject);
   } catch (e) {
       reject(e);
   }
}
function resolvePromise (promise2, x, resolve, reject) {
   if (promise2 === x) { // 防止自己等待自己
       return reject(new TypeError('循环引用了'));
   }
   let called; // 表示Promise有没有被调用过
   // x是object或者是个function
   if ((x !== null && typeof x === 'object') || typeof x === 'function') {
       try {
           let then = x.then;
           if (typeof then === 'function') {
               then.call(x, function (y) {
                   if (called) { // 是否调用过
                       return;
                   }
                   called = true;
                   resolvePromise (promise2, y, resolve, reject)
               }, function (r) {
                   if (called) { // 是否调用过
                       return;
                   }
                   called = true;
                   reject(r);
               });
           } else { // 当前then是一个普通对象。
               resolve(x)
           }
       } catch (e) {
           if (called) { // 是否调用过
               return;
           }
           called = true;
           reject(e);
       }
   } else {
       if (called) { // 是否调用过
           return;
       }
       called = true;
       resolve(x);
   }
}
Primsie.prototype.then = function(onFulfilled, onRejected) {
   onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
   onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
   var self = this;
   const promise2 = new Promise(function (resolve, reject) {
       if (self.status === 'resolved') {
           setTimeout(function() {
               try {
                   const x = onFulfilled(self.value);
                   resolvePromise(promise2, x, resolve, reject);
               } catch(e) {
                   reject(e);
               } 
           }, 0)
       }
       if (self.status === 'rejected') {
           setTimeout(function() {
               try {
                   const x = onRejected(self.reason);
                   resolvePromise(promise2, x, resolve, reject);
               } catch(e) {
                   reject(e);
               }
           }, 0)
       }
       if (self.status === 'pending') {
           self.onResolvedCallbacks.push(function () {
               setTimeout(function() {
                   try {
                       const x = onFulfilled(self.value);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch(e) {
                       reject(e);
                   }
               }, 0)
           });
           self.onRejectedCallbacks.push(function() {
               setTimeout(function() {
                   try {
                       const x = onRejected(self.reason);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch(e) {
                       reject(e);
                   }
               }, 0)
           });
       }
   })
   return promise2;
}

测试

可以使用promises-aplus-tests测试自己书写的Promise是否符合规范。
测试的时候需要提供一段脚本,通过入口进行测试。

Promise.defer = Promise.deferred =  function() {
   let dfd = {};
   dft.promise = new Promise((resolve, reject) => {
       dfd.resolve = resolve;
       dfd.reject = reject;
   });
   return dfd;
}

我们首先安装promises-aplus-tests包。

npm install promises-aplus-tests -g

安装成功执行测试脚本

promises-aplus-tests promise.js

Promise静态方法实现

Promise.all = function (values) {
   return new Promise(function (resolve, reject) {
       var arr = []; // 最终结果的数组
       var index = 0;
       function processData (key, value) {
           index++;
           arr[key] = value;
           if (index === values.length) {
               resolve(arr);
           }
       }
       for (var i = 0; i < values.length; i++) {
           var current = values[i];
           if (current && current.then && typeof current.then === 'function') {
               current.then(function(y) {
                   processData(i, y);
               }, reject);
           } else {
               processData(i, current);
           }
       }
   });
}
Promise.race = function (values) {
   return new Promise(function (resolve, reject) {
       for (var i = 0; i < values.length; i++) {
           var current = values[i];
           if (current && current.then && typeof current.then === 'function') {
               current.then(resolve, reject);
           } else {
               resolve(current);
           }
       }
   });
}
Promise.resolve = function(value){
   return new Promise((resolve,reject)=>{
       resolve(value);
   });
}
Promise.reject = function(reason){
   return new Promise((resolve,reject)=>{
       reject(reason);
   });
}