完全明白Promise对象——用es5语法完成一个本身的Promise(上篇)

本文同步自我的个人博客: http://mly-zju.github.io/

尽人皆知javascript言语的一大特征就是异步,这既是它的长处,同时在某些状况下也带来了一些的题目。最大的题目之一,就是异步操纵过量的时刻,代码内会充溢着众多回调函数,以致构成回调金字塔。为了处置惩罚回调函数带来的题目,Promise作为一种更文雅的异步处置惩罚方案被提出,最初只是一种完成接口范例,而到了es6,则是在言语层面就原生支撑了Promise对象。

最初打仗Promise的时刻,我以为它是比较笼统而且令人困惑的,置信许多人也有一样的觉得。然则在厥后的熟习历程当中,我逐步体味到了它的文雅,并最先思索Promise对象完成的道理,终究用es5语法完成了一个具有基本功能的自身的Promise对象。在这篇文章中,会把自身完成的历程和思绪循规蹈矩的纪录一下,置信人人看完今后,也能够完全明白Promise对象运转的道理,并在今后的开辟中,能更闇练的应用它。

github源码地点: https://github.com/mly-zju/Js-practice

1. 回到过去: resolve, reject和then

起首来看一个Promise的应用实例:

var fn=function(resolve, reject){
  console.log('begin to execute!');
  var number=Math.random();
  if(number<=0.5){
    resolve('less than 0.5');
  }else{
    reject('greater than 0.5');
  }
}

var p=new Promise(fn);
p.then(function(data){
  console.log('resolve: ', data);
}, function(data){
  console.log('reject: ', data);
})

这个例子当中,在fn当中发生一个0~1的随机数,假如小于即是0.5, 则挪用resolve函数,大于0.5,则挪用reject函数。函数定义好今后,用Promise包裹这个函数,返回一个Promise对象,然后挪用对象的then要领,离别定义resolve和reject函数。这里resolve和reject比较简朴,就是把传来的参数加一个前缀然后打印输出。

这里我们须要注重,当运转 p=new Promise(fn)这条语句的时刻,fn函数就已在实行了,然则,p.then这个要领是在背面才定义了resolve和reject,那末为什么fn函数能够晓得resolve和reject函数是什么呢?

换句话说,resolve和reject函数是怎样回到过去,涌现在先实行的fn函数当中的呢?这是Promise当中最重要的一个观点之一。

实在想要完成这个“黑科技”,要领也异常简朴,重要应用的就是setTimeout这个要领,来耽误fn当中resolve和reject的实行。应用这个思绪,我们能够开端写出一个自身的低级版Promise,这里我们命名为MyPromise:

function MyPromise(fn) {
  this.value;
  this.resolveFunc = function() {};
  this.rejectFunc = function() {};
  fn(this.resolve.bind(this), this.reject.bind(this));
}

MyPromise.prototype.resolve = function(val) {
  var self = this;
  self.value=val;
  setTimeout(function() {
    self.resolveFunc(self.value);
  }, 0);
}

MyPromise.prototype.reject = function(val) {
  var self=this;
  self.value=val;
  setTimeout(function() {
    self.rejectFunc(self.value);
  }, 0);
}

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {
  this.resolveFunc = resolveFunc;
  this.rejectFunc = rejectFunc;
}

var fn=function(resolve, reject){
  console.log('begin to execute!');
  var number=Math.random();
  if(number<=0.5){
    resolve('less than 0.5');
  }else{
    reject('greater than 0.5');
  }
}

var p = new MyPromise(fn);
p.then(function(data) {
  console.log('resolve: ', data);
}, function(data) {
  console.log('reject: ', data);
});

能够看出, MyPromise吸收fn函数,并将自身的this.resolve和this.reject要领作为fn的resolve和reject参数传给fn并实行。而我们视察MyPromise的resolve要领,便能够发明,其重要操纵,就是应用setTimeout,耽误0秒实行resolveFunc。

而再来视察then要领,能够看到,这里比较简朴,就是接收两个函数,并离别赋给自身的this.resolveFunc和this.rejectFunc。

这里逻辑就很清晰了,虽然fn函数起首实行,然则因为在挪用resolve和reject的时刻,应用了setTimeout。虽然是耽误0秒实行,然则我们晓得js是单线程+音讯行列,必需等主线程代码实行终了才最先实行音讯行列当中的代码。因而,会起首实行then这个要领,给resolveFunc和rejectFunc赋值。then实行终了后,再实行setTimeout内里的要领,这个时刻,resolveFunc和rejectFunc已被赋值了,所以就能够顺遂实行。这就是“回到过去”的奥妙地点。

2. 到场状况: pending, resolved, rejected

上一节,开端完成了看起来好像能够运转的MyPromise,然则题目许多。我们看一下下面代码:

var fn=function(resolve, reject){
  resolve('hello');
  reject('hello again');
}

var p1=new Promise(fn);
p1.then(function(data){
  console.log('resolve: ',data)
}, function(data){
  console.log('reject: ',data)
});
//'resolve: hello'

var p2=new MyPromise(fn);
p2.then(function(data){
  console.log('resolve: ',data)
}, function(data){
  console.log('reject: ',data)
});
//'resolve: hello '
//'reject: hello again'

p1是原生Promise,p2是我们自身写的,能够看出,当挪用resolve今后再挪用reject,p1只会实行resolve,我们的则是两个都实行。事实上在Promise范例当中,划定Promise只能从初始pending状况变到resolved或许rejected状况,是单向变化的,也就是说实行了resolve就不会再实行reject,反之亦然。

为此,我们须要在MyPromise中到场状况,并在必要的处所举行推断,防备反复实行:

function MyPromise(fn) {
  this.value;
  this.status = 'pending';
  this.resolveFunc = function() {};
  this.rejectFunc = function() {};
  fn(this.resolve.bind(this), this.reject.bind(this));
}

MyPromise.prototype.resolve = function(val) {
  var self = this;
  if (this.status == 'pending') {
    this.status = 'resolved';
    this.value=val;
    setTimeout(function() {
      self.resolveFunc(self.value);
    }, 0);
  }
}

MyPromise.prototype.reject = function(val) {
  var self = this;
  if (this.status == 'pending') {
    this.status = 'rejected';
    this.value=val;
    setTimeout(function() {
      self.rejectFunc(self.value);
    }, 0);
  }
}

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {
  this.resolveFunc = resolveFunc;
  this.rejectFunc = rejectFunc;
}

如许,再次运转上面的实例,就不会涌现resolve和reject都实行的状况了。

3. 链式挪用

在Promise的应用中,我们肯定注重到,是能够链式挪用的:

var fn=function(resolve, reject){
  resolve('hello');
}

var p1=new Promise(fn);
p1.then(function(data){
  console.log(data);
  return 'hello again';
}).then(function(data){
  console.log(data);
});
//'hello'
//'hello again'

很显然,要完成链式挪用,then要领的返回值也必需是一个Promise对象,如许才再次在背面挪用then。因而我们修正MyPromise的then要领:

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {
  var self = this;
  return new MyPromise(function(resolve_next, reject_next) {
    function resolveFuncWrap() {
      var result = resolveFunc(self.value);
      resolve_next(result);
    }
    function rejectFuncWrap() {
      var result = rejectFunc(self.value);
      resolve_next(result);
    }

    self.resolveFunc = resolveFuncWrap;
    self.rejectFunc = rejectFuncWrap;
  })
}

这里能够看出,then返回了一个MyPromise对象。在这个MyPromise当中,包裹了一个函数,这个函数会马上实行,重要做的事变,就是对resolveFunc和rejectFunc举行封装,然后再赋值给前一个MyPromise的resolveFunc和rejectFunc。这里难点是看懂封装的目标。

这里以上面一个例子来申明。在上面的链式挪用例子中,涌现了两个Promise,第一个是我们经由过程new Promise显式定义的,我们叫它Promise 1,而第二个Promise,是Promise 1的then要领返回的一个新的,我们叫它Promise 2 。在Promise 1的resolve要领实行今后,resolve的返回值,会通报给Promise 2的resolve作为参数,这也是为什么上面第二个then中打印出了第一个then返回的字符串。

而我们封装的目标,就是为了让Promise 1的resolve或许reject在实行后,将其返回值通报给Promise 2的resolve。在我们自身的完成中,Promise 2的resolve我们命名为resolve_next,在Promise 1的resolveFunc实行今后,我们拿到返回值result,然后挪用resolve_next(result),通报参数给Promise 2的resolve。这里值得注重的是,不管Promise 1实行的是resolveFunc照样rejectFunc,其今后挪用的,都是Promise 2的resolve,至于Promise 2的reject用来干吗,鄙人面的章节内里我们会详细描述。

至此,我们的MyPromise看起来就能够应用链式挪用了。

然则我们再回去视察Promise范例,会发明链式挪用的状况也分两种。一种状况下,前一个Promise的resolve或许reject的返回值是一般的对象,这类状况下我们现在的MyPromise能够准确处置惩罚。但另有一种状况,就是前一个Promise的resolve或许reject实行后,返回的值自身又是一个Promise对象,举个例子:

var fn=function(resolve, reject){
  resolve('hello');
}

var p1=new Promise(fn);
p1.then(function(data){
  console.log(data);
  return 'hello again';
}).then(function(data){
  console.log(data);
  return new Promise(function(resolve){
    var innerData='hello third time!';
    resolve(innerData);
  })
}).then(function(data){
  console.log(data);
});
//'hello'
//'hello again'
//'hello third time!'

在这个例子当中涌现了两次链式挪用,第一个then返回的是一个’hello again’字符串,在第二个then的resolve中会打印处置惩罚。然后我们注重第二个then当中,返回的是一个Promise对象,挪用了resolve。那末题目来了,这个resolve哪里来呢?答案就是在第三个then当中定义!这个例子中第三个then定义的resolve也比较简朴,就是直接打印传给resolve的参数。

因而,这里我们的MyPromise也须要修正,针对前一个resolve或许reject的返回值做推断,看是不是Promise对象,假如是,就做差别的处置惩罚,修正的代码以下:

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {
  var self = this;
  return new MyPromise(function(resolve_next, reject_next) {
    function resolveFuncWrap() {
      var result = resolveFunc(self.value);
      if (result && typeof result.then === 'function') {
        //假如result是MyPromise对象,则经由过程then将resolve_next和reject_next传给它
        result.then(resolve_next, reject_next);
      } else {
        //假如result是其他对象,则作为参数传给resolve_next
        resolve_next(result);
      }
    }
    function rejectFuncWrap() {
      var result = rejectFunc(self.value);
      if (result && typeof result.then === 'function') {
        //假如result是MyPromise对象,则经由过程then将resolve_next和reject_next传给它
        result.then(resolve_next, reject_next);
      } else {
        //假如result是其他对象,则作为参数传给resolve_next
        resolve_next(result);
      }
    }
    self.resolveFunc = resolveFuncWrap;
    self.rejectFunc = rejectFuncWrap;
  })
}

能够看到在代码中,关于resolveFunc或许rejectFunc的返回值,我们会推断是不是含有.then要领,假如含有,就认为是一个MyPromise对象,从而挪用该MyPromise的then要领,将resolve_next和reject_next传给它。不然,一般对象,result就作为参数传给resolve_next。

如许修正今后,我们的MyPromise就能够在链式挪用中准确的处置惩罚一般对象和MyPromise对象了。

云云,在这篇文章中,我们就起首完成了Promise的经常使用基本功能,重如果then的挪用,状况的掌握,以及链式挪用。而在背面的文章中,还会进一步解说怎样完成Promise的毛病捕捉处置惩罚等等(比方Promise当中的.catch要领道理),从而让我们的MyPromise真正硬朗和可用!

    原文作者:勇敢的半导体
    原文地址: https://segmentfault.com/a/1190000008656872
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞