原生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的部分功能,更複雜的功能我這裡用不到也就沒實現,基本上日常使用夠了!