JS手搓Promise的常见方法总结

 更新时间:2023年09月06日 08:37:57   作者:玛拉_以琳  
这篇文章主要为大家详细介绍了JavaScript中手写Promise的一些常见方法,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下

1. 手搓 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
  // 保存初始化状态
  var self = this;
  // 初始化状态
  this.state = PENDING;
  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;
  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];
  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];
  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;
        // 设置传入的值
        self.value = value;
        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }
  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;
        // 设置传入的值
        self.value = value;
        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }
  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}
MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };
  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }
  // 如果状态已经凝固,则直接执行对应状态的函数
  if (this.state === RESOLVED) {
    onResolved(this.value);
  }
  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

2. 手搓 Promise.then

then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

那么,怎么保证后一个 **then** 里的方法在前一个 **then**(可能是异步)结束之后再执行呢?

我们可以将传给 then 的函数和新 promise 的 resolve 一起 push 到前一个 promise 的 callbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promise 的 resolve,让其状态变更,这又会依次调用新 promise 的 callbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promise 的 resolve,所以可以在其结果的 then 里调用新 promise 的 resolve
then(onFulfilled, onReject){
    // 保存前一个promise的this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封装前一个promise成功时执行的函数
      let fulfilled = () => {
        try{
          const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
        }catch(err){
          reject(err)
        }
      }
      // 封装前一个promise失败时执行的函数
      let rejected = () => {
        try{
          const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){
          reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

3. 手搓 Promise.all

1) 核心思路

  • 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
  • 这个方法返回一个新的 promise 对象,
  • 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  • 参数所有回调成功才是成功,返回值数组与参数顺序一致
  • 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了

function promiseAll(promises) {
  return new Promise(function(resolve, reject) {
    if(!Array.isArray(promises)){
        throw new TypeError(`argument must be a array`)
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedResult = [];
    for (let i = 0; i < promiseNum; i++) {
      Promise.resolve(promises[i]).then(value=>{
        resolvedCounter++;
        resolvedResult[i] = value;
        if (resolvedCounter == promiseNum) {
            return resolve(resolvedResult)
          }
      },error=>{
        return reject(error)
      })
    }
  })
}
// test
let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(2)
    }, 2000)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(3)
    }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
    console.log(res) // [3, 1, 2]
})

4. 手搓 Promise.race

该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.

Promise.race = function (args) {
  return new Promise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}

到此这篇关于JS手搓Promise的常见方法总结的文章就介绍到这了,更多相关JS Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家

相关文章

  • js canvas实现擦除效果示例代码

    js canvas实现擦除效果示例代码

    擦除效果在我们日常开发中也是时有见到的,通过擦除效果大大加强了与用户的交互性,所以下面这篇文章主要给大家介绍了利用js和canvas实现擦除效果的相关资料,文中给出了详细的介绍和示例代码,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-04-04
  • 利用js判断手机是否安装某个app的多种方案

    利用js判断手机是否安装某个app的多种方案

    这篇文章主要介绍了利用js检测手机是否安装某个app的多种方案,当判断后如果安装了直接打开,如果有没有安装将自动跳转到下载的界面,有需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-02-02
  • Vue formData实现图片上传

    Vue formData实现图片上传

    这篇文章主要为大家详细介绍了Vue formData实现图片上传,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • js 数据类型判断的方法

    js 数据类型判断的方法

    这篇文章主要介绍了js 数据类型判断的方法,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下
    2020-12-12
  • 短视频(douyin)去水印工具的实现代码

    短视频(douyin)去水印工具的实现代码

    这篇文章主要介绍了市面上短视频(douyin)"去水印"的工具原来是这样实现的,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • javascript创建动态表单的方法

    javascript创建动态表单的方法

    这篇文章主要介绍了javascript创建动态表单的方法,实例分析了javascript动态操作页面表单元素的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • javascript天然的迭代器

    javascript天然的迭代器

    有一个数n=5,不用for循环,怎么返回[1,2,3,4,5]这样一个数组
    2010-10-10
  • Javascript 变量作用域 两个可能会被忽略的小特性

    Javascript 变量作用域 两个可能会被忽略的小特性

    关于Javascript,大家肯定都很熟悉啦,对于有编程经验的朋友来说,Javascript很快就能上手,不过关于JS的变量作用域,还是有一点差别的。
    2010-03-03
  • JavaScript仿微博输入框效果(案例分析)

    JavaScript仿微博输入框效果(案例分析)

    这篇文章给大家分享一个小的JavaScript的案例,就是模仿微博输入框的效果,非常不错,对微博输入框效果感兴趣的朋友通过本文学习吧
    2016-12-12
  • JS数组扁平化的方法合集(递归,while循环,flat)

    JS数组扁平化的方法合集(递归,while循环,flat)

    数组扁平化也是面试常考题之一,今天就和大家简单分享一下常见的数组扁平方法,这题其实主要考察的是递归思想,因为当数组里面嵌套非常多层数组的时候只能通过循环递归来进行扁平,本次分享主要也是分享本题的递归思想,需要的朋友可以参考下
    2024-06-06

最新评论