Promise 源码:实现一个简单的 Promise

前言

Promise 是 ES6 新增的一个内置对象, 它是用来避免回调地狱的一种解决方案。git

从之前一直嵌套传回调函数,到使用 Promise 来链式异步回调。Promise 到底是怎么实现,从而达到回调函数“扁平化”?github

接下来就来一步步实现一个简单的 Promise。开始发车了...数组

执行步骤

先来看一个使用 Promise 的简单例子:promise

var p = new Promise(function a (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
})

p.then(function b (val) {
  console.log(val);
});
复制代码

代码执行,它会先执行函数 a,打印出 1。定时器 1 秒后执行 resolve,紧接着执行了函数 b。异步

更详细的步骤是这样子的:函数

  1. new Promise,执行 Promise 构造函数;
  2. 构造函数里,执行 a 函数;
  3. 执行 then 函数;
  4. 1 秒后,执行 resolve 函数;
  5. 执行 b 函数。

这里的一个思路就是,在 then 函数执行时用一个属性保存函数 b,而后在 resolve 执行时再将其执行。测试

开始封装

这里定义一个 MyPromise,它有 then 函数,还有一个 callback 属性用来保存上面的“b 函数”。ui

function MyPromise (fn) {
  var _this = this;
  
  // 用来保存 then 传入的回调函数
  this.callback = undefined;
  
  function resolve (val) {
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback = cb;
};
复制代码

测试使用:this

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
});
复制代码

代码执行时会立刻打印出 1,1秒后打印出 2。毛问题。spa

多个 resolve 调用处理

上面已经实现了一个简单的 Promise,固然还有不少种状况须要考虑。

好比会有这么一种状况,调用了多个 resolve 函数:

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
    resolve(3);
    resolve(4);
  }, 1000);
});
复制代码

原生的 Promise 在调用了第一个 resolve 以后,后面的 resolve 都无效化,即后面的 resolve 都是没用的代码。

这里的处理方式是,给 MyPromise 再添加一个属性 isResolved,用来记录是否调用过 resolve 函数。若是调用过,用它标识一下。再有 resolve 的调用,则用它判断返回。

function MyPromise (fn) {
  var _this = this;

  this.callback = undefined;
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}
复制代码

多个 then 处理

继续走,除了能够调用多个 resolve 函数,一样咱们能够调用多个 then 函数。

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

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

p.then(function (val) {
 console.log(val);
});
复制代码

与 resolve 不一样,这里的每个传给 then 的回调函数都会在 1 秒后执行,即 then 函数都有效。代码执行,先打印出 1。1 秒后,打印两个 2。

因此 MyPromise 的 callback 属性需改为数组的格式,保存着每个 then 的回调函数。

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    
    if (_this.callback.length > 0) {
      _this.callback.forEach(function (func) {
        func && func(val);
      });
    }
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback.push(cb);
};
复制代码

在屡次调用 then 时,MyPromise 经过属性 callback 来保存多个回调函数。在 resolve 执行后,再去遍历 callback,将它保存的回调函数逐个执行。

支持 then 链式调用

Promise 相对于回调地狱而言,它的优点在于能够进行 then 的链式调用,从而将回调函数“扁平化”。

好比我有一个异步操做须要在另外一个异步操做后才能执行,只须要继续调用 then 就可以下一步回调。

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
}).then(function (val) {
  console.log(val);
});
复制代码

既然要可以 then 链式调用,那我在执行完 then 函数后返回 this 不就能够啦。但这不就跟刚刚的代码同样了吗?

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

p.then(function (val) {
  console.log(val);
});
复制代码

这样就会在调用 resolve 函数以后同时执行,而不是执行完第一个 then 后,再执行第二个。因此直接返回 this 的方案是不行的。

咱们再想,还有什么能够返回的,而且带有 then 函数的。答案就是 new 一个新的 MyPromise 并返回。咱们需重写一个 then 函数的实现。

MyPromise.prototype.then = function (cb) {
  var _this = this;

  return new MyPromise(function (resolve) {
    _this.callback.push({
      cb: cb,
      resolve: resolve
    });
  });
};
复制代码

这里 callback 从新改写了一下,保存的是 then 的回调函数,和新 new 的 MyPromise 的 resolve 函数。保存的 resolve 函数先不执行,由于咱们知道,它一旦执行了,就会触发传入 then 的回调函数的执行。

同时,MyPromise 构造函数里的 resolve 也须要调整一下:

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
       _this.callback.forEach(function (item) {
       var res;
       var cb = item.cb;
       var resolve = item.resolve;
       
       cb && (res = cb(val));
       resolve && resolve(res);
     });
    }
  }
  
  fn(resolve);
}
复制代码

在执行第一个 then 的回调函数时,将其执行完返回的值,做为保存的 resolve 的参数传入。

p.then(function (val) {
  console.log(val);
  return val + 1;
}).then(function(val) {
  console.log(val);
});
复制代码

这样子,就可以链式的 then 调用。先别急,咱们实现的只是 then 的同步链式调用,而咱们最终要的是异步的链式调用。

咱们须要这样子的:

p.then(function (val) {
  console.log(val);
  
  return new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);  
  });
}).then(function(val) {
  console.log(val);
});
复制代码

先打印出 1,1秒后打印出第一个 then 里的 2,再过多一秒,打印出第二个 then 的 3。

因此,咱们须要在取出原来保存的 cb 返回的值进行判断。若是该值是一个 MyPromise 对象,则调用它的 then,不然跟原来同样调用。

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
      _this.callback.forEach(function (item) {
        var res;
        var cb = item.cb;
        var resolve = item.resolve;
        
        cb && (res = cb(val));
        if (typeof res === 'object' && res.then) {
          res.then(resolve);
        } else {
          resolve && resolve(res);
        }
      });
    }
  }
  
  fn(resolve);
}
复制代码

最后

在咱们实现的 MyPromise 里,有两个属性,分别是 isResolvedcallback。isResolved 是一个标识,用来防止屡次调用 resolve。callback 是一个数组,用来保存回调函数。

MyPromise 还有一个 then 函数,用来处理异步后的回调,可以链式异步调用。

这样子就实现了一个简单的 Promise,完整的代码戳 这里

相关文章
相关标签/搜索