原生js实现Promise

  • 2019 年 10 月 14 日
  • 筆記

由于浏览器兼容性的限制,我们不得不通过原生js实现Promise方法。

原生的Promise对象包含promise,promiseAll,rase等方法,下面的代码基本上实现了这些方法,但在细微处可能有所区别,主要是为了方便项目使用才这么设计。

promise

promise方法接收一个函数作为参数(如果参数传错会进入异常捕获阶段),参数会被分裂成两个变量一个resolve, reject。改方法返回当前的promise的实例,可以实现链式操作。

具体使用如下

 mrChart.promise(function(resolve, reject){          setTimeout(function(){              resolve('3秒后弹出成功!')          },3000)          setTimeout(function () {              reject('4秒后弹出失败!')          }, 4000)      }).then(function(a) {          console.log(a[1])          alert(a[1])      },function(a){          console.log(a[1])          alert(a[1])      })

关于then方法的使用是接收两个参数(都为函数),第一个参数对应于方法resolve,第二个参数对应于reject,如果不传或者传少了,代码会自动给你生成一个空函数,但是这个函数是捕获不到信息的。

 promise.all

promise.all方法是为了监听多个promise对象设计的,它接收多个promise作为参数,以实现多个等待的效果

假如我们创建三个promise

var a = mrChart.promise(function (resolve, reject) {          setTimeout(function () {              resolve('3秒后弹出成功!')          }, 3000)      })  var b = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          reject('4秒后失败!')      }, 4000)  })  var c = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          resolve('5秒后弹出成功!')      }, 5000)  })

我们创建一个promise.all方法,监听上面多个promise的对象状态,只有所有的promise都会成功了才会进入到all方法的成功回调,否则会reject(失败)

代码如下:

var d = mrChart.promise.all(a,b,c).then(function(){      console.log('全部都成功了',arguments)  }).catch(function(){      console.log('其中有失败的', arguments);  })

 promise.rase

还有一个方法rase,字面上是奔跑的意思,我理解是单步rase,可以称之为管道的概念,只有其中一个失败或成功(这取决于谁先跑完),类似于看谁先完成就有谁来决定这次promise的状态,正常业务中用到的场景不是很多,这里也只是简单的实现一下,可能具体细节还实现的不好,有兴趣的可以在上面进行扩展。

这里我们看下代码使用的例子

//单步跑 rase  var a = mrChart.promise(function (resolve, reject) {          setTimeout(function () {              resolve('3秒后弹出成功!')          }, 3000)      })  var b = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          reject('4秒后失败!')      }, 2000)  })  var d = mrChart.promise.race(a,b).then(function(){      console.log('谁快执行哪一个--成功的',arguments)  },function(){      console.log('谁快执行哪一个--失败的', arguments)  }).catch(function(){      console.log('单个异常会成为当前rase的异常');  })

 本插件中还用到发布订阅Emiter,通过这个来辅助完成promise的状态

Emiter实现如下:

//事件订阅区域       function Emiter(){          this._events = Object.create(null);       }       Emiter.prototype.on = function(event, fn){           var vm = this;           if (Array.isArray(event)) {               for (var i = 0, l = event.length; i < l; i++) {                   vm.on(event[i], fn);               }           } else {               (vm._events[event] || (vm._events[event] = [])).push(fn);           }           return vm       }       Emiter.prototype.once = function (event, fn) {            var vm = this;           function on() {               vm.off(event, on);               fn.apply(vm, arguments);           }           on.fn = fn;           vm.on(event, on);           return vm       }       Emiter.prototype.off = function (event, fn) {           var vm = this;           // all           if (!arguments.length) {               vm._events = Object.create(null);               return vm           }           // array of events           if (Array.isArray(event)) {               for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {                   vm.off(event[i$1], fn);               }               return vm           }           // specific event           var cbs = vm._events[event];           if (!cbs) {               return vm           }           if (!fn) {               vm._events[event] = null;               return vm           }           // specific handler           var cb;           var i = cbs.length;           while (i--) {               cb = cbs[i];               if (cb === fn || cb.fn === fn) {                   cbs.splice(i, 1);                   break               }           }           return vm       }       Emiter.prototype.emit = function (event) {           var vm = this;           var cbs = vm._events[event];           if (cbs) {               cbs = cbs.length > 1 ? toArray(cbs) : cbs;               var args = toArray(arguments, 1);               for (var i = 0, l = cbs.length; i < l; i++) {                   cbs[i].apply(vm,args);               }           }else{               error('[mrChart error]:Chart:Emiter.emit event is not found');           }           return vm       }

Emiter源码参考vue2.0的源码实现

下面是完整的代码
/*!      * 针对图形化框架设计的promise 插件      * @version   1.0.0      *      */  (function (global, factory) {      typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :          typeof define === 'function' && define.amd ? define(['exports'], factory) :              (factory((global.mrChart = global.mrChart || {})));  }(this, (function (exports) {      'use strict';      function toArray(list, start) {          start = start || 0;          var i = list.length - start;          var ret = new Array(i);          while (i--) {              ret[i] = list[i + start];          }          return ret      }      function noop(a, b, c) { }        var hasConsole = typeof console === 'object'        function log() {          if (hasConsole) {              Function.apply.call(console.log, console, arguments)          }      }        function warn() {          if (hasConsole) {              var method = console.warn || console.log              // http://qiang106.iteye.com/blog/1721425              Function.apply.call(method, console, arguments)          }      }        function error(str, e) {          throw (e || Error)(str)      }          function isObject(input) {          // IE8 will treat undefined and null as object if it wasn't for          // input != null          return input != null && Object.prototype.toString.call(input) === '[object Object]';      }        function isFunction(input) {          return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';      }         //事件订阅区域       function Emiter(){          this._events = Object.create(null);       }       Emiter.prototype.on = function(event, fn){           var vm = this;           if (Array.isArray(event)) {               for (var i = 0, l = event.length; i < l; i++) {                   vm.on(event[i], fn);               }           } else {               (vm._events[event] || (vm._events[event] = [])).push(fn);           }           return vm       }       Emiter.prototype.once = function (event, fn) {            var vm = this;           function on() {               vm.off(event, on);               fn.apply(vm, arguments);           }           on.fn = fn;           vm.on(event, on);           return vm       }       Emiter.prototype.off = function (event, fn) {           var vm = this;           // all           if (!arguments.length) {               vm._events = Object.create(null);               return vm           }           // array of events           if (Array.isArray(event)) {               for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {                   vm.off(event[i$1], fn);               }               return vm           }           // specific event           var cbs = vm._events[event];           if (!cbs) {               return vm           }           if (!fn) {               vm._events[event] = null;               return vm           }           // specific handler           var cb;           var i = cbs.length;           while (i--) {               cb = cbs[i];               if (cb === fn || cb.fn === fn) {                   cbs.splice(i, 1);                   break               }           }           return vm       }       Emiter.prototype.emit = function (event) {           var vm = this;           var cbs = vm._events[event];           if (cbs) {               cbs = cbs.length > 1 ? toArray(cbs) : cbs;               var args = toArray(arguments, 1);               for (var i = 0, l = cbs.length; i < l; i++) {                   cbs[i].apply(vm,args);               }           }else{               error('[mrChart error]:Chart:Emiter.emit event is not found');           }           return vm       }        var pN = 'MrPromise';        function isPromise(n){          return n.isPromise || false      }        function promiseResolve(){          var promise = this;          return function(){              try {                  promise.state = 'resolve';                  promise.callbacks[ 0 ]([arguments, 'resolve']);                  promise.data = arguments;                  if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [arguments, promise.state])              } catch (e) {                  promise.state = 'catch';                  promise.callbacks[ 2 ]([e, 'catch'])                  promise.data = e;                  if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [e, promise.state])              }              return promise;          }      }        function promiseReject() {          var promise = this;          return function () {              try {                  promise.state = 'reject';                  promise.callbacks[ 1 ]([arguments, 'reject'])                  promise.data = arguments;                  if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [arguments, promise.state])              } catch (e) {                  promise.state = 'catch';                  promise.callbacks[ 2 ]([e, 'catch'])                  promise.data = e;                  if (promise.emitName !== '') promise.Emit.emit(promise.emitName, [e, promise.state])              }              return promise;          }      }        function promiseResolveAll(){          var promise = this;          return function () {              promise.state = 'resolve';              promise.callbacks[0]([arguments, 'resolve']);              return promise;          }      }        function promiseRejectAll() {          var promise = this;          return function () {              promise.state = 'catch';              promise.callbacks[1]([arguments, 'catch'])              return promise;          }      }        function promiseResolveRace() {          var promise = this;          return function () {              promise.state = 'resolve';              promise.callbacks[0]([arguments, 'resolve']);              return promise;          }      }        function promiseRejectRace() {          var promise = this;          return function () {              promise.state = 'reject';              promise.callbacks[1]([arguments, 'reject'])              return promise;          }      }        function promiseAll(dependent){          // 初始promise状态          this.state = 'pending';          this.isPromise = true;          this.dependent = dependent;          // resolve catch==reject          this.callbacks = [noop, noop];          this.resolve = promiseResolveAll.call(this),          this.reject = promiseRejectAll.call(this);          this.Emit = new Emiter();          this.emitName = 'ResolveState';          for (var i = 0; i < dependent.length;i++){              dependent[i].bindEmit(this.Emit,'ResolveState');          }          var promise = this;          var reason = [new Array(dependent.length), new Array(1)];          this.Emit.on(this.emitName,function(){              var n = 0,m = 0;              for (var i = 0; i < promise.dependent.length;i++){                  var state = promise.dependent[i].state                  if (state === 'resolve'){                      n++;                      reason[0][i] = promise.dependent[i].data;                  } else if (state !== 'pending'){                      reason[0] = new Array(promise.dependent.length)                      reason[1][0] = promise.dependent[i].data;                  }                  if (state !== 'pending') m++              }              if (n === promise.dependent.length){                  promise.resolve(reason[0])                  reason = [new Array(dependent.length), new Array(1)];              } else if (m === promise.dependent.length){                  promise.reject(reason[1])                  reason = [new Array(dependent.length), new Array(1)];              }          });          return this;      }        promiseAll.prototype = {          constructor: promiseAll,          then:function(){              var args = arguments;              if (isFunction(args[0])) this.callbacks[0] = args[0]              return this;          },          catch:function(){              var args = arguments;              if (isFunction(args[0])) this.callbacks[1] = args[0]              return this;          }      }        function MrPromise(fn) {          // 初始promise状态          this.state = 'pending';          this.isPromise = true;          // resolve reject catch          this.callbacks = [noop,noop,noop];          this.reject =          this.resolve = noop;          this.Emit = {              emit: noop          };          this.data = '';          this.emitName = '';          this.resolve = promiseResolve.call(this),          this.reject = promiseReject.call(this);          fn(this.resolve, this.reject);          return this;      }        function PromiseRace(dependent){          // 初始promise状态          this.state = 'pending';          this.isPromise = true;          this.dependent = dependent;          // resolve reject catch          this.callbacks = [noop, noop, noop];          this.resolve = promiseResolveRace.call(this),          this.reject = promiseRejectRace.call(this);          this.Emit = new Emiter();          this.emitName = 'ResolveStateRace';          for (var i = 0; i < dependent.length; i++) {              dependent[i].bindEmit(this.Emit, 'ResolveStateRace');          }          var promise = this;          var reason = [new Array(1), new Array(1)];          this.Emit.on(this.emitName, function (data) {              if(promise.state !== 'pending') return;              if (data[1] === 'resolve'){                  promise.resolve(data[0])              }              if (data[1] === 'reject') {                  promise.reject(data[0])              }              if (data[1] === 'catch') {                  promise.state = 'catch';                  promise.callbacks[2](data[0])              }          });          return this;      }        PromiseRace.prototype = {          constructor: PromiseRace,          then: function () {              var args = arguments;              if (isFunction(args[0])) this.callbacks[0] = args[0]              if (isFunction(args[1])) this.callbacks[1] = args[1]              return this;          },          catch: function () {              var args = arguments;              if (isFunction(args[0])) this.callbacks[2] = args[0]              return this;          }      }        MrPromise.promise = function(){          var args = arguments,              l = args.length,              fn = args[0];          if (!l) error(pN + '() 请传入一个函数');          if (l > 1) warn(pN + '() 参数长度为1');          if (!isFunction(fn)) error(pN + '() 参数类型不是一个函数');          return new MrPromise(fn)      }        //单步跑promise      MrPromise.promise.race = function(){          var dependent = []; //依赖promise          var args = arguments,              isAllPromise = false,              n = 0,              l = args.length;          if (!l) error('promiseRace() 参数长度至少1')          for (var i = 0; i < args.length; i++) {              if (isPromise(args[i])) {                  n++                  dependent.push(args[i])              }          }          if (n == l) isAllPromise = true          if (!isAllPromise) error('promiseRace() 参数必须为promise对象')          return new PromiseRace(dependent);      }        MrPromise.promise.all = function(){          var dependent = []; //依赖promise          var args = arguments,              isAllPromise = false,              n = 0,              l = args.length;          if (!l) error('promiseAll() 参数长度至少1')          for (var i = 0; i < args.length; i++) {              if (isPromise(args[i])) {                  n++                  dependent.push(args[i])              }          }          if (n == l) isAllPromise = true          if (!isAllPromise) error('promiseAll() 参数必须为promise对象')          return new promiseAll(dependent);      }            MrPromise.prototype = {          constructor: MrPromise,          then:function(){              var args = arguments;              if (isFunction(args[ 0 ])) this.callbacks[ 0 ] = args[ 0 ]              if (isFunction(args[ 1 ])) this.callbacks[ 1 ] = args[ 1 ]              return this;          },            catch:function(){              var args = arguments;              if (isFunction(args[0])) this.callbacks[ 2 ] = args[0]              return this;          },            bindEmit:function(emit,name){              this.Emit = emit;              this.emitName = name;              return this;          }      }        exports.promise = MrPromise.promise;      Object.defineProperty(exports, '__esModule', { value: true });    })));

下面是完整使用的demo代码

/*测试promise*/  /*      mrChart.promise(function(resolve, reject){          setTimeout(function(){              resolve('3秒后弹出成功!')          },3000)          setTimeout(function () {              reject('4秒后弹出失败!')          }, 4000)      }).then(function(a) {          console.log(a[1])          alert(a[1])      },function(a){          console.log(a[1])          alert(a[1])      })  */  /*      var a = mrChart.promise(function(resolve, reject){          setTimeout(function () {              resolve('3秒后弹出成功!')          }, 3000)          setTimeout(function() {              reject('4秒后弹出成功!')          }, 4000);      })      .then(function(){          return c(); //这里故意写错 导致异常抛出      },function(){          console.log(11111)      })      .catch(function(){          //会进入这里进行捕获          console.log(arguments)      })  */  //var c = mrChart.promise('1',2); 异常捕获测试  //var c = mrChart.promise(); 异常捕获测试  /*  //promise.all 测试  var a = mrChart.promise(function (resolve, reject) {          setTimeout(function () {              resolve('3秒后弹出成功!')          }, 3000)      })  var b = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          reject('4秒后失败!')      }, 4000)  })  var c = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          resolve('5秒后弹出成功!')      }, 5000)  })  var d = mrChart.promise.all(a,b,c).then(function(){      console.log('全部都成功了',arguments)  }).catch(function(){      console.log('其中有失败的', arguments);  })  */  /*  //单步跑 rase  var a = mrChart.promise(function (resolve, reject) {          setTimeout(function () {              resolve('3秒后弹出成功!')          }, 3000)      })  var b = mrChart.promise(function (resolve, reject) {      setTimeout(function () {          reject('4秒后失败!')      }, 2000)  })  var d = mrChart.promise.race(a,b).then(function(){      console.log('谁快执行哪一个--成功的',arguments)  },function(){      console.log('谁快执行哪一个--失败的', arguments)  }).catch(function(){      console.log('单个异常会成为当前rase的异常');  })  */

以上只是简单实现了promise的部分功能,更复杂的功能我这里用不到也就没实现,基本上日常使用够了!