资讯专栏INFORMATION COLUMN

实现一个自己的Promise

leone / 3188人阅读

摘要:前言在写这个之前,希望你已经对中的很熟悉了,概念性和基础的东西就不再讲了,不懂的同学可以去看看阮一峰老师的教程我主要按以下个步骤来一步一步实现,异步的实现我放在了后面,所以前面几步暂不考虑实现一个基本的实现的链式调用处理函数的参数是实例的情

前言

在写这个promise之前,希望你已经对es6中的Promise很熟悉了,概念性和基础的东西就不再讲了,不懂的同学可以去看看阮一峰老师的es6教程. 我主要按以下5个步骤来一步一步实现,异步的实现我放在了后面,所以前面几步暂不考虑

实现一个基本的MyPromise

实现then的链式调用

处理reolve函数的参数是MyPromise实例的情况以及处理then方法中前一个回调函数返回的也是一个MyPromise实例的情况

实现异步的MyPromise

完善MyPromise的其它方法

1.实现一个基本的MyPromise
/*
 * 这里我将promise的3个状态分别定义为: pending, resolved, rejected
 * 其中fn必须是个函数, 必须通过new来使用
 */
function MyPromise(fn) {
  if (!(this instanceof MyPromise)) {
    throw new TypeError("MyPromise must be constructed via new");
  }
  if (typeof fn !== "function") {
    throw new TypeError("MyPromise constructor argument is not a function");
  }
  this.state = "pending";  // 出初始化状态
  this.value = undefined;  // 初始化一个值, 用来存储resolve或者reject的值
  // 执行 fn 方法
  executeFn(fn, this);
}

MyPromise.prototype.then = function(onFullfilled, onRejected) {
  var res = undefined;
  var cb = this.state === "resolved" ? onFullfilled : onRejected;
  res = cb(this.value);
}

// 执行 fn 方法
function executeFn(fn, promise) {
  var done = false;     // 声明一个变量, 防止resolve, reject连续调用
  try {
    fn(function _resolve(value) {
      if(done) return;
      done = true;
      resolve(promise, value);
    }, function _reject(reason) {
      if(done) return;
      done = true;
      reject(promise, reason);
    });
  }
  catch(err) {
    if(!done) {
      done = true;
      reject(promise, err);
    }
  }
}

function resolve(promise, value) {
  promise.state = "resolved";
  promise.value = value;
}

function reject(promise, error) {
  promise.state = "rejected";
  promise.value = error;
}

这样就实现了一个基本版的MyPromise,调用方法同Promise,如下:

new MyPromise((resolve, reject) => {
  // resolve("resolved");
  reject("rejected");
}).then(res => {
  console.log(">>> res", res);
}, err =>  {
  console.log(">>> err", err);
});
2.实现then的链式调用

原生Promise支持链式调用,并且then方法会返回一个新的Promise实例,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数,所以我们可以修改下then方法,其余不变

MyPromise.prototype.then = function(onFullfilled, onRejected) {
  var self = this;
  var res = undefined;
  var cb = this.state === "resolved" ? onFullfilled : onRejected;
  var newPromise = new MyPromise(function(_resolve, _reject) {
    try {
      res = cb(self.value);
      _resolve(res);
    }
    catch(err) {
      _reject(err);
    }
  });

  return newPromise;
}

这样的话,链式调用也就实现了,测试了下,没啥问题

new MyPromise((resolve, reject) => {
  resolve("resolved");
  // reject("rejected");
}).then(res => {
  console.log(">>> res", res);
  return "res1";
}, err => {
  console.log(">>> err", err);
  return "err1";
}).then(res2 => {
  console.log(">>> res2", res2);
 }, err2 => {
  console.log(">>> err2", err2);
});
3. 处理reolve函数的参数是MyPromise实例的情况以及处理then方法中前一个回调函数返回的也是一个MyPromise实例的情况

resolve函数的参数除了正常的值以外,还可能是另一个MyPromise实例,如下,这个时候p1的状态决定了p2的状态

const p1 = new MyPromise(function(resolve, reject) {
  // ...
});

const p2 = new MyPromise(function(resolve, reject) {
  // ...
  resolve(p1);
});

同样,在then方法中,前一个回调函数有可能返回的也是一个MyPromise对象,这时后一个回调函数就会等待该MyPromise对象的状态发生变化,才会被调用,例如:

const p1 = new MyPromise(function(resolve, reject) {
  // ...
});

const p2 = new MyPromise(function(resolve, reject) {
  // ...
  resolve("p2 resolved");
});
p2.then(res1 => {
  console.log(">>> res1", res1);
  return p1;
}, err1 => {
  console.log(">>> err1", err1);
}).then(res2 => {
  console.log(">>> res2", res2);
}, err2 => {
  console.log(">>> err2", err2);
})

了解以上后,我们来实现它

function resolve(promise, value) {
  if(!handlePromise(promise, value)) return;  // 增加这行

  promise.state = "resolved";
  promise.value = value;
}
/*
 * 增加一个函数用来处理返回值或者resolve的参数是promise的情况, 最后的返回值起个标识作用
 */
function handlePromise(promise, value) {
  if(value === promise) {
    reject(promise, "A promise cannot be resolved with itself");
    return;
  }
  if(value && (typeof value === "object" || typeof value === "function")) {
    var then = value.then;
    if(typeof then === "function") {
      executeFn(then.bind(value), promise);
      return;
    }
  }
  return true;
}
// 同时then中增加一行代码
MyPromise.prototype.then = function(onFullfilled, onRejected) {
  var self = this;
  var res = undefined;
  var cb = this.state === "resolved" ? onFullfilled : onRejected;
  var newPromise = new MyPromise(function(_resolve, _reject) {
    if(self.state === "pending") return;  // 增加这行
    try {
      res = cb(self.value);
      _resolve(res);
    }
    catch(err) {
      _reject(err);
    }
  });

  return newPromise;
}

到这里,就解决了以上2个问题,测试一下

const p = new MyPromise((resolve, reject) => {
  // resolve("resolve a promise");
  reject("reject a promise");
});
// resolve参数是MyPromise实例
new MyPromise((resolve, reject) => {
  resolve(p);
  // reject("rejected");
}).then(res => {
  console.log(">>> res", res);
  return "res1";
}, err => {
  console.log(">>> err", err);
  return "err1";
}).then(res2 => {
  console.log(">>> res2", res2);
}, err2 => {
  console.log(">>> err2", err2);
});

// then第一个方法返回的是MyPromise实例
new MyPromise((resolve, reject) => {
  resolve("resolved");
  // reject("rejected");
}).then(res => {
  console.log(">>> res", res);
  return p;
}, err => {
  console.log(">>> err", err);
  return "err1";
}).then(res2 => {
  console.log(">>> res2", res2);
}, err2 => {
  console.log(">>> err2", err2);
});
4. 实现异步的MyPromise

经过前面3步,MyPromise的已经实现了大半,接着我们来实现异步的MyPromise. 既然是异步,我们并不知道它什么时候结束,但是我们可以将它的异步回调存入一个数组,待它结束后执行它,好吧,其实就是观察者模式了

首先在构造函数中加上一句

function MyPromise(fn) {
  // ...这里省略,加上下面这行
  this.callbacks = [];     // 存储异步的回调方法
  // 执行 fn 方法
  executeFn(fn, this);
}

然后在resolve和reject方法中分别加上

function resolve(promise, value) {
  // ...这里省略,加上下面几句
  promise.callbacks.forEach(function(cb) {
    cb();
  });
}
function reject(promise, error) {
  // ...这里省略,加上下面几句
  promise.callbacks.forEach(function(cb) {
    cb();
  });
}

最后在then方法中,将异步的回调发存入数组中

MyPromise.prototype.then = function(onFullfilled, onRejected) {
  // ...这里省略,不变
  var newPromise = new MyPromise(function(_resolve, _reject) {
    // 状态为pending时,将回调存入数组,因为then中方法也是异步执行
    // 所以用setTimeout,同时直接return
    if(self.state === "pending") {
      self.callbacks.push(function() {
        setTimeout(function() {
          // 这里需要再次判断
          cb = self.state === "resolved" ? onFullfilled : onRejected;
          try {
            res = cb(self.value);
            _resolve(res);
          }
          catch(err) {
            _reject(err);
          }
        });
      });
      return;
    }
    // then中是异步执行
    setTimeout(function() {
      try {
        res = cb(self.value);
        _resolve(res);
      }
      catch(err) {
        _reject(err);
      }
    });
  });

  return newPromise;
}

到这里,异步的MyPromise也就实现了,then方法代码有点乱,我们整理下

MyPromise.prototype.then = function(onFullfilled, onRejected) {
  // 这里删除了一部分代码
  var self = this;
  var newPromise = new MyPromise(function(_resolve, _reject) {
    if(self.state === "pending") {
      self.callbacks.push(function() {
        // 同时将这部分的代码抽成了以下方法
        handleResolved(self, onFullfilled, onRejected, _resolve, _reject);
      });
      return;
    }
    handleResolved(self, onFullfilled, onRejected, _resolve, _reject);
  });

  return newPromise;
}
function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) {
  setTimeout(function() {
    var res = undefined;
    var cb = promise.state === "resolved" ? onFullfilled : onRejected;
    // 需要对cb进行判断
    if(typeof cb !== "function") {
      if(promise.state === "resolved") {
        _resolve(promise.value);
      }
      else {
        _reject(promise.value);
      }
      return;
    }
    try {
      res = cb(promise.value);
      _resolve(res);
    }
    catch(err) {
      _reject(err);
    }
  });
}

测试如下, 当然没啥问题了

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("resolve a promise");
    // reject("reject a promise");
  }, 2 * 1000);
});

new MyPromise((resolve, reject) => {
  resolve(p);
  // reject("rejected");
}).then(res => {
  console.log(">>> res", res);
  return "res1";
}, err => {
  console.log(">>> err", err);
  return "err1";
}).then(res2 => {
  console.log(">>> res2", res2);
}, err2 => {
  console.log(">>> err2", err2);
});

到这里,一个大概的MyPromise也就基本实现完成了,整理后的完整代码如下

function MyPromise(fn) {
  if (!(this instanceof MyPromise)) {
    throw new TypeError("MyPromise must be constructed via new");
  }
  if (typeof fn !== "function") {
    throw new TypeError("MyPromise constructor argument is not a function");
  }
  this.state = "pending";  // 初始化状态
  this.value = undefined;  // 初始化一个值, 用来存储resolve或者reject的值
  this.callbacks = [];     // 存储异步的回调方法
  // 执行 fn 方法
  executeFn(fn, this);
}

MyPromise.prototype.then = function(onFullfilled, onRejected) {
  var self = this;
  var newPromise = new MyPromise(function(_resolve, _reject) {
    if(self.state === "pending") {
      self.callbacks.push(function() {
        handleResolved(self, onFullfilled, onRejected, _resolve, _reject);
      });
      return;
    }
    handleResolved(self, onFullfilled, onRejected, _resolve, _reject);
  });

  return newPromise;
}

function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) {
  setTimeout(function() {
    var res = undefined;
    var cb = promise.state === "resolved" ? onFullfilled : onRejected;
    if(typeof cb !== "function") {
      if(promise.state === "resolved") {
        _resolve(promise.value);
      }
      else {
        _reject(promise.value);
      }
      return;
    }
    try {
      res = cb(promise.value);
      _resolve(res);
    }
    catch(err) {
      _reject(err);
    }
  });
}

// 执行 fn 方法
function executeFn(fn, promise) {
  var done = false;     // 声明一个变量, 防止resolve, reject连续调用
  try {
    fn(function _resolve(value) {
      if(done) return;
      done = true;
      resolve(promise, value);
    }, function _reject(reason) {
      if(done) return;
      done = true;
      reject(promise, reason);
    });
  }
  catch(err) {
    if(!done) {
      done = true;
      reject(promise, err);
    }
  }
}

function resolve(promise, value) {
  if(!handlePromise(promise, value)) return;

  promise.state = "resolved";
  promise.value = value;
  promise.callbacks.forEach(function(cb) {
    cb();
  });
}

function reject(promise, error) {
  promise.state = "rejected";
  promise.value = error;
  promise.callbacks.forEach(function(cb) {
    cb();
  });
}

// 用来处理返回值或者resolve的参数是promise的情况, 最后的返回值起个标识作用
function handlePromise(promise, value) {
  if(value === promise) {
    reject(promise, "A promise cannot be resolved with itself");
    return;
  }
  if(value && (typeof value === "object" || typeof value === "function")) {
    var then = value.then;
    if(typeof then === "function") {
      executeFn(then.bind(value), promise);
      return;
    }
  }
  return true;
}
5. 最后来实现下MyPromise的其它方法
MyPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}

// 只要不是pending状态都会执行
MyPromise.prototype.finally = function(cb) {
  return this.then(
    function(value) {
      return MyPromise.resolve(cb()).then(function() {
        return value;
      });
    },
    function(err) {
      return MyPromise.resolve(cb()).then(function() {
        throw err;
      });
    }
  );
}

MyPromise.resolve = function(val) {
  return new MyPromise(function(resolve, reject) {
    resolve(val);
  });
}

MyPromise.reject = function(err) {
  return new MyPromise(function(resolve, reject) {
    reject(err);
  });
}

/*
 * all方法用于将多个 MyPromise 实例,包装成一个新的 MyPromise 实例
 * 只有全部实例都resolved,才会resolve; 只要其中一个rejected,就会reject
 * 参数可以不是数组,但必须具有 Iterator 接口, 同时里面的值可能也不是promise实例
 */
MyPromise.all = function(promiseArr) {
  var args = [].slice.call(promiseArr);

  return new MyPromise(function(resolve, reject) {
    var arr = [];
    var resolveCount = 0;
    var argsLen = args.length;
    for(var i = 0; i < argsLen; i++) {
      handle(i, args[i]);
    }
    function handle(index, val) {
      MyPromise.resolve(val).then(function(value) {
        arr[index] = value;
        if(++resolveCount === argsLen) {
          resolve(arr);
        }
      }, reject);
    }  
  });
}

/*
 * race方法与all方法类似,只要其中一个实例状态发生改变resolved / rejected即可
 * 参数可以不是数组,但必须具有 Iterator 接口, 同时里面的值可能也不是promise实例
 */
MyPromise.race = function(promiseArr) {
  var args = [].slice.call(promiseArr);
  return new MyPromise(function(resolve, reject) {
    for(var i = 0; i < args.length; i++) {
      MyPromise.resolve(args[i]).then(resolve, reject);
    }
  });
}

至此Promise的实现就算完成了,完整代码的地址点这里

最后

说点题外话,在面试的过程中,经常会遇见面试官要求现场实现一个Promise,看了这篇文章后希望对你有所帮助,已经能够实现一个Promise了,而对于面试中其他的promise的相关问题能够轻松应对

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99981.html

相关文章

  • 我能不能在不看别人怎么实现promise情况下,自己实现一个promise

    摘要:上网查了一下好像是标准第二步开写构造函数什么都不想,先写一个构造函数,就叫把。对构造函数再次做修改。并且可以一个值。给下一个继续调用。应该是一个新的。最后的版本总结后来去看了看别人实现的方法。 我能不能在不看别人怎么实现promise的情况下,自己实现一个promise? 都8102年为什么还要写promise实现? ​ 年前和年后面试了几家公司, 虽然都挂了… 但是都谈到了一个...

    JouyPub 评论0 收藏0
  • 从0开始构建自己前端知识体系-JS-跟着规范学Promise

    摘要:本文仅限浏览器环境测试,环境可能会不一致状态一个实例只能处于三种状态中的一种。每次创建的实例都会处于状态,并且只能由变为或状态。可以认为在实现里与中的都为解决程序。 前言 Promise作为ES6极为重要的一个特性,将我们从无限的回调地狱中解脱出来,变为链式的编写回调,大大提高的代码的可读性。 使用Promise是极为简单的,但只停留在会使用阶段还是会让我们不知不觉踩到一些坑的。本文会...

    kelvinlee 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • 自己动手实现一个Promise

    摘要:意味着操作成功完成。状态的对象可能触发状态并传递一个值给相应的状态处理方法,也可能触发失败状态并传递失败信息。测试用例测试用例方法返回一个带有拒绝原因参数的对象。 Promise基本用法 Promise 对象是一个代理对象,被代理的值在Promise对象创建时可能是未知的。 它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值...

    Yujiaao 评论0 收藏0
  • 彻底理解Promise对象——用es5语法实现一个自己Promise(上篇)

    摘要:链式调用在的使用中,我们一定注意到,是可以链式调用的很显然,要实现链式调用,方法的返回值也必须是一个对象,这样才能再次在后面调用。一种情况下,前一个的或者的返回值是普通的对象,这种情况下我们目前的可以正确处理。 本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的...

    YJNldm 评论0 收藏0
  • 从零开始实现一个自己Promise

    摘要:所以,这篇文章我会带大家从零开始,手写一个基本能用的。首先,规定对象是一个构造函数,用来生成实例。然后,这个构造函数接受一个函数作为参数,该函数的两个参数分别是和。对象通过自身的状态,来控制异步操作。 刚开始写前端的时候,处理异步请求经常用callback,简单又顺手。后来写着写着就抛弃了callback,开始用promise来处理异步问题。promise写起来确实更加优美,但由于缺乏...

    paulquei 评论0 收藏0

发表评论

0条评论

leone

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<