javascript 请求-Promise的分析和实现

吐槽君 分类:javascript

Promise 在日常使用中非常广泛,自己也是零零散散的学习了一部分,直到有一天自己查看axios的源码时,发现里面大量的应用了Promise的函数,决定系统的学习一下。本文主要是根据Promise/A+的规范,进行Promise的学习和实现;

Promise介绍

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

特点:

  • 对象不受外界影响,共包含三种状态pengding(进行中,初始状态),fulfilled(已成功)、reject(已失败);只有异步操作结果,才能够改变当前的状态,其他的手段无法改变
  • 一旦状态改变,就不会在变,其中只包含两种可能的状态变化 pending->fulfilled和pending变为reject,只要有这两种情况发生,状态就会凝固,不会在发生改变了

缺点:

  • 无法取消``promise````,一旦新建就会立刻执行无法中途取消;
  • 如果不设置回调函数,内部发生错误不会反应到外部;
  • 处于pending的状态时候,无法得到目前是哪一个阶段;

promise的手写实现

想要实现某个内容,就应该明确Promise的方法使用;
使用方法可参照:es6.ruanyifeng.com/#docs/promi…
本次手写也是参照此用法,对输入和输出进行的控制
Promise 作为一个构造函数,其在new的时候就是立刻进行执行,根据其属性和行为去构建基础执行流程,new Promise()本身是一个同步的函数,而Promise.then才是异步的函数

基础状态确认

查看Promise A+规范对于状态的要求
Promies/A+规范要求:

  • Promise的状态是pending的时候,可能会转化到 fulfilled或者rejected状态
  • Promise状态是filfilled的时候
    • 不能转化成其他的状态
    • 必须返回一个value,并且这个value保持不变
  • Promise的状态是reject的时候
    • 也无法转变成其他的状态
    • 必须返回一个失败的原因,

定义Promise的常量

const PENDING = 'pending';
const RESOLVED = 'fulfilled'; //成功
const REJECTED = 'rejected' //失败
 

创建Promise 的构造函数

关键点

  • 定义当前状态变量status
  • 定义resolvereject来接收成功和失败时候状态更改
  • new Promise时候返回成功和失败状态;
//创建Promise的基本类
class Promise {
  //看这个属性 能够在原型上使用 看属性是否公用
  constructor(executor) {
    this.status = PENDING;
    //成功的值
    this.value = undefined;
    //失败的原因
    this.reason = undefined;
 
    //回调函数存储器 主要解决异步处理流程
    this.onReslovedCb = []; //成功回调
    this.onRejectedCb = []; //失败回调

    //成功函数
    let resolve = (value) => {
    //只有在pending的时候才可以调用
      if (this.status == PENDING) {
        this.value = value;
        this.status = RESOLVED;
        this.onReslovedCb.forEach(fn => fn())
      }
    }
    //失败函数
    let reject = (reason) => {
        //只有在pending的时候才可以调用
      if (this.status == PENDING) {
        this.reason = reason;
        this.status = REJECTED
        this.onRejectedCb.forEach(fn => fn())
      }
    }
    try {
      //执行器 默认会立即执行
      executor && executor(resolve, reject);
    } catch (e) {
      //执行的时候出现错误
      reject(e)
    }
  }
}
 

Promise.prototype.then

关键点then 方法是Promise已经失败/成功时候调用
先看下promise/A+的规范 粗略列举了重要的内容

promise.then(onFulfilled, onRejected)
 
  • onFulfilled,onRejected是then两个参数
    • 如果onFulfilled 不是函数,将会被直接忽略
    • 同理 onRejected不是函数,也会被直接忽略
  • onFulfilled 是函数
    • 当promise的状态是成功状态的时候,其将会被回调,返回的value会是第一个参数
    • 不能被进行调用在其他的状态,而且只能调用一次
  • onRejected是函数的时候
    • 当promise状态是失败时候被调用,失败的原因是第一个参数
    • 不能在其他的状态下被调用,只能被调用一次
  • then方法能够在同一个promise上能够被调用多次
    • 如果当前promise的状态是fulfilled/onRejected,所有的then回调都必须按照他们的调用初始顺序执行
  • then方法 必须返回一个promise

其中onFulfilledonRejected是我们在外部调用的时候传递进行的回调函数;

then(onfulfilled, onrejected) {
    //1. 参数是可选则的参数 需要进行判断是否存在
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : data => data
    onrejected = typeof onrejected == 'function' ? onrejected : error => {
      throw error
    }
    // 2.如果存在回调函数,则执行内部的回调函数,里面的函数会立刻执行
    let promise2 = new Promise((resolve, reject) => {
      //2.1 Promise 返回成功的时候 
      if (this.status == RESOLVED) { 
        // 定时器处理异常 为了保障promise2已经用完了
         setTimeout(() => {
          //try 执行函数的时候会报错 在then里面的数据
          try {
            //x 需要判断是否是promise和规整化  
            let x = onfulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      //2.1-失败的时候
      if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let x = onrejected && onrejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      // 2,1- pending 如果当前是pending 表示异步的请求还没回来,进行收集内容
      if (this.status == PENDING) {
        //如果是异步 先订阅好
        this.onReslovedCb.push(() => {
          //todo... 
          setTimeout(() => {
            try {
              let x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.onRejectedCb.push(() => {
          //todo...

          setTimeout(() => {
            try {
              let x = onrejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }

          }, 0)
        })
      }

    })
    return promise2;
  }
 

then方法返回一个Promise的函数,注意在进行promise2的创建的时候,我们在进行处理时候可能获取的到的是underfined的promise2,因此需要开辟宏任务,promise2创建完成的时候在进行调用,而在进行处理的时候,我们在onfulfilled、和onrejected得到的参数可能不同,他们收到的参数可能为几种

.then(data=>{
    return value;
},err=>{
    return value
}) 
 
  • value 可能是一个Promise,需要执行当前的Promise
  • value 字符串
  • value为使用者输入,可能存在的值也是不确定的,因此需要进行判断,而onfulfilled、onRejected调用后的结果也是不确定的,因此需要进行类型的判断;

then的关键点在于resolvePromise 的函数,这个函数的做用到底是什么呢?

//判断then里面的函数返回值来进行判断  x表示当前onreject
//promise都遵循的规范,因此需要进行兼容写法
function resolvePromise(promise2, x, resolve, reject) {
  //判断当前的x是不是promise  是不是同一个 如果是同一个 就不要等待来了 
  if (promise2 === x) {
    return reject(new TypeError("调用存在错误"))
  }
  //如果x是对象或者函数 判断数据类型 
  /**
   * typeof 基本类型
   * constructor 
   * instanceof 判断实例
   * Object.toString
   */
  if (typeof x === 'object' && typeof x !== null || typeof x == 'function') {
    let called; //内部测试的时候,会成功和失败都调用一下 
    try {
      //取返回结果 then有可能通过defineProperty定义的
      let then = x.then
      //当前存在then方法 姑且是Promise
      if (typeof then === 'function') {
        //绑定this 到返回的x上,保证不用再次取then的值
        then.call(x, y => {
          if (called) return;
          called = true; //防止多次调用成功和失败
          //y可能还是promise  //采用promise的成功结果向下传递
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return;
          called = true;
          reject(r) //采用失败结果乡下传递
        }) //保证再次取到then的值
      } else {
        //说明x就是一个普通的对象 直接成功即可
        resolve(x)
      }
    } catch (e) {
      //promise 失败 还能进行调用成功
      //是一个普通的值 直接让promise2成功即可
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    return resolve(x)
  }
}
 

在then方法执行完后,Promise的实例状态就会改变成resolved、或者reject,此时then方法需要兼容一异步的调用类型.因此,当进入then函数后,如果当前的promise的状态仍然是Pending,则表示当前结果还没有返回,因此需要增加onRejectedCb、onReslovedCb用来存储当前的执行函数,一旦某一个状态改变,则进行调用该存储列表中的数据,进行回调;

注意点

  • then方法中的状态收集和修改
  • onfulfilled/onrejected 返回的参数类型

Promise.prototype.finally

无论Promise返回成功还是失败,都会执行该方法

  • finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作;
  • finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

因此可以绑定此事件在当前promise实例的then方法上,在成功的时候回调传入的函数,在失败的时候也进行回调传入的参数;

/**
 *  finally 函数 promise m每次执行后都会进行执行
 * @param {*} cb 
 */
Promise.prototype.finally = function (cb) {
  //finally 传入函数,无论成功或者失败都会执行 
  return this.then(data => {
    //Promise.resolve 可以等待这个promise完成
    return Promise.resolve(cb().then(() => data))
  }, err => {
      //失败的时候也执行
    return Promise.reject(cb().then(() => {
      throw err
    }))
  })
}
 

Promise.prototype.catch

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

//异常处理 用于指定发生错误时的回调函数。
//promise抛出一个错误,就被catch()方法指定的回调函数捕获
Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}
 

Promise.all

Promise.all可用于接收一个数组作为参数,参数可以不是数组,但是必须有Iterator接口,且返回的每个成员都是Promise的实例,他的结果是根据传入的数据进行变化的

const p = Promise.all([p1, p2, p3]);
 
  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
/**
 * 全部成功才能成功,一个失败才会失败
 * promiseList 表示当前传递的数组对象
 */
Promise.all = function (promiseList) {

  return new Promise((resolve, reject) => {
    let arr = [];
    let index = 0;
    //解决多个异步并发的问题 计数器
    function proceessData(key, value) {
      arr[key] = value;
      if (++index == promiseList.length) {
        resolve(arr)
      }
    }
    for (let i = 0; i < promiseList.length; i++) {
      let current = promiseList[i];
      if (isPromise(current)) {
        current.then((data) => {
          proceessData(i, data)
        }, (err) => {
          console.log("data")
          reject(err)
        })
      } else {
        proceessData(i, current)
      }
    }
  })
}

function isPromise(value) {
  if ((typeof value === 'object' && value !== null) || typeof value === 'function') {
    if (typeof value.then == 'function') {
      return true
    }
  }
  return false;
}
 

Promise.race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。从字面意义上而言,“管道”返回最先得到的那个

const p = Promise.race([p1, p2, p3]);
 
  • 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
/**
 * 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
 *  @param {array} promiseList 传递的参数列表对象
 */
Promise.race = function (promiseList) {
  // console.log(promiseList)
  //将values中的内容包装成promise的 
  if (!Array.isArray(promiseList)) {
    return Promise.resolve();
  }
  promiseList = promiseList.map(item => {
    return !isPromise(item) ? Promise.resolve(item) : item;
  });
  // 有一个实例率先改变状态则进行操作   
  return new Promise((resolve, reject) => {
    promiseList.forEach((pro, index) => {
      pro.then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    })
  })
}
 

Promise.allSettled

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。

/**
 * 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,
 * 不管是fulfilled还是rejected,包装实例才会结束
 */
Promise.allSettled = function (promiseList) {
  return new Promise((resolve, reject) => {
    let index = 0;
    let arr = [] ;
    //用于记录当前的promise的执行状态
    function recordRequest(key, value) {
      index++;
      arr[key] = value;
      //选择这种计数的方式,主要是考虑存在异步的流程,等待所有流程都执行完成后在结束
      if (index == promiseList.length) {
        resolve(arr)
      }
    }
    for (let i = 0; i < promiseList.length; i++) {
      current = promiseList[i]
      if (isPromise(current)) {
        current.then((data) => {
        //每执行完成一个,就去增加记录
          recordRequest(i, {
            status: 'resolve',
            value: data
          })
        }, (err) => {
           //失败的promise也记录
          recordRequest(i, {
            status: 'reject',
            reason: err
          })
        })
      } else {
        recordRequest(i, {
          status: '',
          value: current
        })
      }
    }
  })
  }
 

Promise.any

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。

  • 一失败全失败
  • 所有成功才成功

/**
 * Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。
 * @param {*} promiseList promise的参数列表
 */
Promise.any = function(promiseList){
  promiseList = promiseList.map(item => {
    return !isPromise(item) ? Promise.resolve(item) : item;
  });
  let index = 0; 
  let result=[]
  return new Promise((resolve,reject)=>{
    for (let i = 0; i < promiseList.length; i++) {
      current = promiseList[i]
      if (isPromise(current)) {
        current.then((data) => {
          resolve(data)
        }, (err) => { 
          index++; 
          result.push(err)
          if(index == promiseList.length){
            reject(err);
          } 
        })
      } 
    }
  })
}
 

Promise.resolve

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。会返回一个状态为Resolved状态的promise
Promise.resolve(value),其中value的值包含好多种

  • 参数是一个 Promise 实例
  • 参数是一个thenable对象
  • 参数不是具有then()方法的对象,或根本就不是对象
  • 不带有任何参数

参数的类型可能存在几种情况

/**
 * Promis.resolve 函数
 * @param {*} values 传递进来的变量函数
 */
Promise.resolve = function (values) {
  //1.参数是一个 Promise 实例 将原封不动的返回
  if (values instanceof Promise) {
    return values;
  }
  return new Promise((resolve, reject) => {
    //2.参数是一个含有then对象 具有then方法
    //Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
    if (isPromise(values)) {
      values.then(resolve, reject);
    } else {
      //3.参数不是具有then()方法的对象,或根本就不是对象  如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
      //4.参数不是具有then()方法的对象,或根本就不是对象
      //5.不带有任何参数 
      resolve(values)
    }
  })
}
 

Promise.reject

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

/**
 * //Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
 * 参数为values字符串
 */
Promise.reject = function (values) {
  return new Promise((resolve, reject) => {
      reject(values)
  })
}
 

Promise.try

实用场景:
不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,用Promise.try包装一下,可以更好地管理异常。

Promise.try = function (fn, argumnts = null, ...args) {
  if (typeof fn == 'function') {
  //立刻执行fn函数并进行调用返回
    return new Promise(resolve => resolve(fn.apply(argumnts, args)))
  } else {
    const err = new TypeError(`${typeof fn} ${fn} is not a function`);
    return Promise.try(() => {
      throw err
    });
  }
}
 

源码位置

面试频点

  • 和setTimeout 进行 打印输出判断(微任务、宏任务执行顺序) 非常高频
    • let p = new Promise() 本身同步执行
    • p.then()才是异步的执行
  • Promise.then 的实现
  • Promise实现多个请求同时进行
  • Promise的三种状态变化
  • Promise的常见api
  • Promise解决的问题痛点

参考文档

  • 阮一峰-Promise的使用
  • Promise A+规范
  • b站视频Promise的实现讲解

回复

我来回复
  • 暂无回复内容