用es6实现一个符合Promise/A+规范的myPromise库

本文深入讲解Promise的工作原理,包括状态转换、链式调用、错误处理等核心概念,并通过实战案例展示如何实现自定义Promise库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 简介

  1. Promise是异步编程async的一种解决方案;
  2. 使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观,避免回调地狱;
  3. 兼容低版本:$.Deferred 或 polyfill类库;

1.1 promise 三种状态

  1. Fulfilled 成功态
  2. Rejected 失败态
  3. Pending 初状态

1.2 promise 组成

  • 构造函数: new Promise
  • 实例方法: .then()、.catch()
  • 静态方法: Promise.resolve、Promise.reject、Promise.all、Promise.race;

1.3 promise 特点

  • 对象状态只由异步操作结果决定。
  • resolve方法会使Promise对象由pendding状态变Fulfilled状态;
  • reject方法或者异常会使得Promise对象由pendding状态变为rejected状态。
  • Promise状态变化只有这两条路径。
  • 对象状态一旦改变,任何时候都能得到这个结果。即状态一旦进入Fulfilled或者Rejected,promise便不再出现状态变化,同时我们再添加回调会立即得到结果。
  • 这跟事件不一样,事件是发生后再绑定监听,就监听不到了。

2. 原理

let p = new Promise((resolve, reject) => {
  if (Math.random() > 0.5) {
    resove();
  } else {
    reject();
  }
});
p.then(() => {
  //p的状态被resolved
}, () => {
  //p的状态被reject
});
复制代码
1. 调用Promise构造函数,构造实例;
    1.1 实例的三种状态:
        1) pending      (待定)
        2) fulfilled    (已执行) resolve
        3) rejected     (已拒绝) reject
        只有状态变成已完成(即fulfilled和rejected之一),才能触发对应状态的回调;
2. 构造函数的参数是个函数,此函数接受两个回调函数作为参数,分别为resolve和reject;
3. 在参数函数被执行的过程中:
    3.1 如果在其内部调用resolve,会将p的状态变成fulfilled;
    3.2 如果在其内部调用reject,会将p的状态变成rejected;
4. .then() 实例调用:
    4.1 then中,会为实例注册两种状态的回调函数;
    4.2 当实例的状态为fulfilled,  会触发第一个函数;
    4.3 当实例的状态为rejected,  会触发第一个函数;
    4.4 返回一个promise对象(链式调用)
    4.5 在注册状态的回调函数中:
        - 使用return: 改变.then返回的promise对象的状态,以及向后面.then注册的状态回调传递数据
        - 不使用return: 默认返回的promise对象resolve
5. .catch()
    5.1 注册rejected状态的回调函数;
    5.2 也是程序出错的回调;
    5.3 返回新的promise对象;
6. .resolve()
    6.1 返回一个fulfilled状态的promise对象;
7. .reject()
    7.1 返回一个rejected状态的promise对象;
复制代码

3. 自己动手实现一个Promise

3.1 基本版

  • 思路:
  1. 在判断状态为pending之后把状态改为相应的值;
  2. 把对应的value和reason存在self的data属性上面;
  3. 然后后执行相应的回调函数;
function Promise (executor) {
  var self = this;
  self.status = 'pending';
  self.data = undefined;
  self.onResolvedCallback = [];  //Promise resolved 回调函数集
  self.onRejectedCallback = [];

  function resolve (value) {
    if (self.status == 'pending') {
      self.status = 'fulfiled';
      self.data = value;
      self.onResolvedCallback.forEach(task => {
        task(self.data);
      })
    }
  }
  function reject (reason) {
    if (self.status == 'pending') {
      self.status = 'rejected';
      self.data = reason;
      self.onRejectedCallback.forEach(task => {
        task(self.data);
      })
    }
  }
复制代码

3.2 then方法

链式调用的特点:

  • 将then返回值作为下一次成功的回调函数的参数;

  • then 方法是用来注册在这个Promise状态确定后的回调;

  • 思路:

    1. 需要将then写在原型链prototype上;
    2. then返回一个新的Promise对象;
function  myPromise (executor) {
  var self = this;
  self.status = 'pending';
  self.value = undefined;
  self.reason = undefined;
  self.onResolvedCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve (value) {
    if (self.status == 'pending') {
      self.value = value;
      self.status = 'fulfilled';
      self.onResolvedCallbacks.forEach(task => task(value));
    }
  }
  function reject (reason) {
    if (self.status == 'pending') {
      self.status = 'rejected';
      self.reason = reason;
      self.onRejectedCallbacks.forEach(task => task(reason));
    }
  }
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}
myPromise.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  let promise2;
  if (self.status == 'fulfilled') {
    return promise2 = new myPromise((resolve, reject) => {
      try {
        var x = onFulfilled(self.value);
        if (x instanceof myPromise) {
          // 如果是个promise对象的话,调用then让函数执行;
          // 这个resolve,reject分别是promise2的成功和失败的回调;
          x.then(resolve, reject);  //这就相当于p2中resolve('promise')
        } else {
          resolve(x);
        }
      } catch (err) {
        reject(err);
      }
    })
  }

  if (self.status == 'rejected') {
    return promise2 = new myPromise((resolve, reject) => {
      try {
        var x = onRejected(self.reason);
        if (x instanceof myPromise) {
          x.then(resolve, reject);
        } else {
          resolve(x);  //注意这里也是resolve,失败的结果也会作为下一次then成功的参数
        }
      } catch (err) {
        reject(err);
      }
    })
  }

  if (self.status == 'pending') {
    return promise2 = new myPromise((resolve, reject) => {
      //因为这里还不知道pending会走向哪里,所以需要存储到回调函数集中
      self.onResolvedCallbacks.push(() => {
        var x = onFulfilled(self.value);
        if (x instanceof myPromise) {
          x.then(resolve, reject);
        } else {
          resolve(x);
        }
      })
      self.onRejectedCallbacks.push(() => {
        var x = onRejected(self.reason);
        if (x instanceof myPromise) {
          x.then(resolve, reject);
        } else {
          resolve(x);
        }
      })
    })
  }
};


module.exports = myPromise;
复制代码

调用代码

var myPromise = require('./myPromise.js');

var p1 = new myPromise((resolve, reject) => {
  /*if (Math.random > 0.5) {
    resolve(100);
  } else {
    reject('错了');
  }*/
  setTimeout(() => {
    resolve(100)
  },1000)
});

let p2 = p1.then((data) => {
  //return data + 100
  return new myPromise((resolve, reject) => {
    resolve('这是使用promise对象作为下次执行的结果');
  })
});

p2.then((data) => {
  console.log(data);
});

复制代码

3.3 Promise值的穿透

解决如下问题:

new Promise(resolve => resolve(8))
  .then()
  .catch()
  .then(value => console.log(value))
复制代码
解决方式:
onResolved = typeof onFulfilled === 'function' ? onFulfilled :  (value) => value;
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => throw reason;
复制代码

3.4 catch方法

'.'

catch方法是then(onFulfilled, onRejected) 方法当中 onRejected 函数的一个简单的写法

即Promise.prototype.catch 是 Promise.then(null,reject)的别名

p.catch(function(err) { // todo ... })
等价于
p.then(null, function(err) {//to do ...})

复制代码

.'.

myPromise.prototype.catch = function (func) {
  return this.then(null, func);
}
复制代码

3.5 与其它Promise库结合

'.'

  • 不同的Promise实现之间需要无缝的可交互;
  • 如我们自己实现的Promise,与ES6的Promise,以及其它的Promise库,应该能够无缝相互调用的;
var myPromise = require('./myPromise.js');

var p1 = new myPromise((resolve, reject) => {
  resolve(100);
});

let p2 = p1.then((data) => {
  return new Promise((resolve, reject) => {  //使用原生的Promise
    resolve('123');     
  });
});
p2.then((data) => {
  console.log(data); //结果是 Promise { '123' }
});
复制代码

.'.

  1. 将所有判断返回值是否为Promise对象的地方,改写成:
/* if (x instanceof myPromise) {
    x.then(resolve, reject);
} else {
    resolve(x);
}  */

改写成:
resolvePromise(promise2, x, resolve, reject);
复制代码

实现resolvePromise

思路: 将返回的Promise不断的执行,直到失败或者返回一个普通的值

/**
 * promise2 一个新的、自定义的、myPromise对象
 * x  then方法返回的结果 有可能是普通的值,也有可能是promise对象
 * resolve 成功的回调
 * reject 失败的回调
 * */
function resolvePromise (promise2, x, resolve, reject) {
  if (promise2 === x) {
    throw new Error('两个对象不能来自同一个引用!!!');
  }
  if ((x != null) && (typeof x === 'function') || (typeof x === 'object')) {
    let then = x.then;
    if (typeof then === 'function') {
      then.call(x, (value) => {
        //这里value也有可能是个promise, 所以需要进行递归
        resolvePromise (promise2, value, resolve, reject)
      }, (reason) => {
        reject(reason);
      })
    }
  } else {
    resolve(x);
  }
}
复制代码

3.6 Promise.all

  • 同样返回一个新的Promise对象;
  • 可以传入多个promise,可以是数组或对象,但必须是可以迭代的;
  • 全部执行完成会将结果以数组的方式返回(按传入顺序,按顺序返回结果);
  • 如果有一个失败了就失败了,即一旦其中一个promise出错,整个promise会被reject;
var myPromise = require('./myPromise.js');
var p1 = new myPromise((resolve, reject) => {
  setTimeout(() => {
    // resolve(1);
    reject('错了')
  },3000)
});

var p2 = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  },1000)
});

myPromise.all([
  p1,
  p2
]).then((value) => {
  console.log(value);
}, (reason) => {
  console.log(reason);
});

// 注意:以上异步不是4s后才返回结果,而是3s就返回结果
复制代码
myPromise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let res = [];
    let nIndex = 0; //完成所有promise任务的计数器
    let resolved = (idx) => {
      return (data) => {
        res[idx] = data;
        nIndex++;
        if (nIndex === promises.length) {
          resolve(res);  //抛出结果
        }
      }
    };

    promises.forEach((promise, idx) => {
      promise.then(resolved(idx), (reason) => reject(reason)); //有一个失败,就立即reject
    })
  });
}
复制代码

3.7 Promise.race

  • 同样返回一个新的Promise对象
  • 可以传入多个promise;
  • 参数数组中,一个promise对象状态改变,则这个新的Promise对象的状态也转化为该状态
var p1 = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  },5000)
});

var p2 = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  },1000)
});

var p3 = new myPromise((resolve, reject) => {
  setTimeout(() => {
    reject(3);
  },500)
});

myPromise.race([
  p1,
  p2,
  p3
]).then((value) => {
  console.log(value);
}, (reason) => {
  console.log(reason);
});

复制代码

3.8 Promise.resolve

  • 是Promise.resolve(value)的快捷方式;
是以下代码的语法糖
new Promise((resolve) => {
    resolve(10)
});

再如:
var obj = {
  then: (resolve) => resolve('执行了')
};

var resolved = Promise.resolve(obj);
==> 等价的
var resolved = new Promise((resolve, reject) => {
  obj.then(resolve, reject);
});

resolved.then((str) => {
  console.log(str)
});
复制代码
  • 即将状态立即转换为fulfilled状态,执行onFulfilled函数;
  • 同样返回一个新的Promise对象
myPromise.resolve(10).then((value) => {
  console.log(value);
})
复制代码
myPromise.resolve = function (value) {
  return new myPromise((resolve, reject) => {
    if (value && (typeof value === 'function') || (typeof value === 'object')) {
      value.then(resolve, reject);
    } else {
      resolve(value);
    }
  })
}

复制代码

3.9 Promise.reject

  • 同样返回一个新的Promise对象
Promise.reject('错了').catch(function(result) {
    console.log(result);
});;
p
复制代码
myPromise.reject = function (value) {
  return new myPromise((resolve, reject) => {
    reject(value);
  });
};
复制代码

应用场景

  1. 实际工作中使用promise进行了后台数据模拟;
  2. Promise.all,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联;
  3. 网页中预加载20张图片资源,分步加载,一次加载10张,两次完成,怎么控制图片请求的并发,怎样感知当前异步请求是否已完成?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值