手写promise和promise的方法

140 阅读8分钟

手写promise

//构建了三个常量用于表示状态,对于经常使用的一些值都应该通过常量来管理,便于开发和后期维护
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
    // 在函数内部首先创建了常量that,因为函数可能会异步执行,用于获取正确的this对象
    const that = this;
    // 一开始Promise的状态应该是pending
    that.state = PENDING;
    // value变量用于保存resolve或者rekect中传入的值
    that.value = null;
    // resolvedCallbacks和rejectCallbacks用于保存then中的回调,因为当执行完Promise的状态可能还在等待中,这时候应该把then中的回调保存起来用于状态改变时使用
    that.resolvedCallbacks = [];
    that.rejectedCallbacks = [];
    // 完善resolve和reject函数
    function resolve(value) {
        if (that.state === PENDING) {
            that.state = RESOLVED;
            that.value = value;
            that.resolvedCallbacks.map((cb) => cb(that.value));
        }
    }

    function reject(value) {
        if (that.state === PENDING) {
            that.state = REJECTED;
            that.value = value;
            that.rejectedCallbacks.map((cb) => cb(that.value));
        }
    }
    // 执行传入的参数并且将之前两个函数当作参数传进去
    // 可能执行函数过程中会遇到错误,需要捕获出错文件并且执行reject函数
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    const that = this;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
    onRejected =
        typeof onRejected === 'function'
            ? onRejected
            : (r) => {
                  throw r;
              };
    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled);
        that.rejectedCallbacks.push(onRejected);
    }
    if (that.state === RESOLVED) {
        onFulfilled(that.value);
    }
    if (that.state === REJECTED) {
        onRejected(that.value);
    }
};

function create(Con,...args) {
    // 首先函数接受不确定数量的参数,第一个参数为构造函数,其他参数为构造函数所用
    // 内部创建一个空对象
    let obj = {};
    // 因为obj需要访问到构造函数原型链上的属性,所以通过原型量将两者联系起来
    // 下面这两句的效果是一样的
    // obj.__proto__ = Con.prototype;
    Object.setPrototypeOf(obj,Con.prototype);
    // 将this指向obj对象
    let result = Con.call(obj, ...args);
    // 判断构造函数返回的是否是函数,如果不是,则返回obj,这样实现了,忽略构造函数返回原始值的操作
    return result instanceof Object ? result : obj;
}




let promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject(2);
    }, 2000);
}).then(
    (res) => {
        console.log(res);
    },
    (errmsg) => {
        console.log(errmsg);
    }
);

promise的方法

Promise.prototype.fianlly()

  1. 不管promise最后的状态是什么,都会执行的操作
  2. 回调函数不接受参数,所以就没有办法知道前面promise的状态是resolved还是rejected
  3. finally是then的特殊状态
promise.finally(() => {
  // 语句
});
// 等同于
promise.then(
  result => {// 语句
    return result;},
  error => // 语句
    throw error});

Promise.all()

const p = Promise.all([p1, p2, p3]);

  1. 用于将多个promise对象,包装成一个promise对象的方法,接受一个数组作为一个参数 如果参数不是promise对象,则会先调用resolve方法,将参数转化为promise实例, 参数可以不是数组,但是必须有Interator接口,且返回的实例必须为promise对象

  2. p的状态由p1,p2,p3的状态决定的

  • 只有p1,p2,p3的状态都变为fulfilled,p的状态才会变为fulfilled,并且将p1,p2,p3的返回值组成一个数组,传递给p的回调函数
  • 只要p1,p2,p3的状态有一个为rejected,则p的值为rejected,第一个被rejected的值传递给p的回调函数
  1. 所以当比如有6个promise的时候,只有这6个都变为fulfilled,或者其中一个变为rejected,才会调用到all后面的方法

  2. ⚠️:如果作为参数的promise对象自己定义了promise对象的catch方法,那么,即使返回了catch,也不会进入all方法内,如果作为参数的promise没有catch方法,则会进入all的方法。

Promise.race

const p = Promise.race([p1, p2, p3]);

  1. 只要参数里面有一个参数的值的状态发生了变化,那么就会调用race里面的方法 率先改变的Promise实例的值会返回

  2. 例子:如果5秒内,fetch没有返回值,则会返回rejected,从而出发catch里面的函数

Promise.allSettled()

  1. 接受一组promise实例作为参数,包装成一个新的promise实例,是有当这些参数全部返回结果,不管是fulfilled还是rejected,包装实例才会结束

  2. 结束后,返回一个新的promise实例,实例的状态总是fulfilled,不会是rejected,promise实例接收到的参数是一个数组

用法的例子:
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);

// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');

// 过滤出失败的请求,并输出原因
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

Promise.any

  1. 接受一组promise实例作为参数,这组参数有一个变为fulfilled,则状态变为fulfilled,只有所有的状态均变为rejected,结果状态才会变为rejected状态

  2. 与Promise.race的区别: Promise.race,不会由于某个promise的状态变为rejected而结束

Promise.resolve()

  1. 有时需要将现有对象转化为promise对象,resolve就是起这个作用 const jsPromise = Promise.resolve($.ajax('/whatever.json')); 以上代码就是将ajax形成的deferred对象转化为promise对象

  2. 参数分为4种情况

  • 参数是一个promise实例,不做任何处理,原封不动的返回

  • 参数是一个thenable对象

// thenable对象:具有then方法的对象
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

会将对象转化为promise对象,然后立即执行thenable对象的then方法

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
  • 参数不是具有then方法的实例,或者根本不是对象,如果参数是一个初始值或者不具有then方法的对象,则返回一个promise对象,状态为resolve状态
const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello
// 会将resolve的参数作为参数传给then方法
  • 不带任何参数 直接返回一个resolved状态的promise对象 所以,如果希望得到一个promise对象,比较方便的方法是可以直接调用promise.resolve()

⚠️:立即resolve的对象,会在本次事件轮询结束的时候执行,而不是下一次事件轮询开始的时候

// 例如:
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

Promise.reject()

方法会返回一个promise对象,对象的状态为rejected

promise.rejected()的参数会直接作为rejected的理由,这个与resolve方法不同

例如:
const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

// promise对象的参数不是传进来的“出错了”字符串,而是thenable方法

Promise.try()

  1. 要解决的问题 不知道或者不清楚某一个函数是否含有异步操作,想用promise进行处理,这样,不管是否含有异步操作,都可以用then指定下一步流程,用catch来处理错误

如果使用Promise.resolve()引起的问题

// 缺点:同步操作会在本次事件轮训的最后执行
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

解决方法:同步的同步处理,异步的异步处理 方法一:用async处理

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next

如果f是异步的,可以使用then来处理后面的操作 (async () => f())() .then(...)

需要注意的是:这种操作会吃掉错误,所以要想要捕获错误,要使用catch()进行捕获

方法二:使用自执行函数

方法二:new Promise()
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
  1. 提出try的好处

更好的错误处理

同步代码的异常,无论在何处出现,都会以rejection的方式向promise链后段传递

function getUsername(userId) { return database.users.get({id: userId}) .then(function(user) { return uesr.name; }); } 这样写的话,uesr拼写错误,这行会出现异常,这个异常会被.then()捕获,并被转化为rejected promise 但是如果database.users.get同步的抛出了异常呢?如果在第三方的代码里存在拼写错误或是其他问题呢?Promises的错误捕获机制的前提是使用者编写的所有同步代码都要放在.then中,这样它就可以将这些同步代码放入一个try/catch块中 但是。。。我们代码中的database.users.get并不在.then块中。因此Promise就不能访问这部分代码也就不能将它们包裹在try/catch中。这就导致同步异常不能被转变为异步异常。我们又回到了原点,不得不处理两种形式的异常 —— 同步的和异步的

使用了promise.try()

var Promise = require("bluebird");

function getUsername(userId) { return Promise.try(function() { return database.users.get({id: userID}); }).then(function(user) { return user.name; }); } 我前边说过Promise.try很像.then,但是它不需要跟在Promise后边。除此之外,它还会捕获database.users.get中的同步异常,就像.then一样! 通过引入Promise.try,我们已经把我们代码的错误处理由只能捕获第一次异步操作(第一次.then调用)之后的异常改造为可以覆盖所有的同步异常。

更好的兼容性,可是使用自己喜欢的promise实现,而不用担心第三方在使用哪个

更好的代码阅读体验 都在同一个缩紧上,比较好阅读

Promise.all和Promise.allsettled的区别

Promise.all方法只有在接受的所有Promise实例的状态都是fulfilled才会走,只要有一个的状态是rejected就会走catch方法。

Promise.allsettled方法无论promise实例的状态是fulfilled还是rejected都会走。

Promise.race和Promise.any的区别

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise ,关注是被解决还是被拒绝。

Promise.race()方法主要关注 Promise 是否已解决