什么是Promise?要不来个Promise A+ 加深一下

前言

最近打蛋在面试某场面试中,面试官问了一道让打蛋 <math xmlns="http://www.w3.org/1998/Math/MathML"> 措手不及 \color{red}措手不及 </math>措手不及(搬砖太久脑生锈了😁)的问题,什么是Promise❓,说说你的理解

在每个前端仔日常开发中,一定逃不过Promise,比如网络请求、文件读取 ,Promise的存在也解决了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 横向回调地狱 \color{red}横向回调地狱 </math>横向回调地狱的问题(有使用过原生ajax的同学就深有体会),回到标题,什么是Promise呢?

什么是Promise

引用MDN上的定义:Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。

我们知道Promise有三个状态, pending(初始状态)、fulfilled(成功)、rejected(失败),并且状态只能从pending -> fulfilled、pending -> rejected,并且状态改变后就无法再次变更

模拟实现Promise

按照PromiseA+的规范,我们知道Promise有几个基础api,then(成功)、catch(失败)、resolve、reject,话不多不多、直接上代码:

这里有几个注意的点:

  1. 当Promise状态改变为fulfilled(成功)、rejected(失败)后抛出的值仍是Promise,仍需要进行递归处理
  2. 当Promise状态仍未pending时,需要在then里面将成功失败的回调推进各自的队列,等待对应状态变更执行
PromiseA+.js 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
  constructor(executor){
    // 储存状态的变量,初始值是 pending
    this.status = PENDING;
    // 成功之后的值
    this.value = null;
    // 失败之后的原因
    this.eason = null;
    // 存储成功回调函数
    this.onFulfilledCallbacks = [];
    // 存储失败回调函数
    this.onRejectedCallbacks = [];
    const resolve = value => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
          // 状态修改为成功
          this.status = FULFILLED;
          // 保存成功之后的值
          this.value = value;
          // resolve里面将所有成功的回调拿出来执行
          while (this.onFulfilledCallbacks.length) {
            // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
            this.onFulfilledCallbacks.shift()(value)
          }h
        }
    }

    // 更改失败后的状态
    const reject = (reason) => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
            // 状态成功为失败
            this.status = REJECTED;
            // 保存失败后的原因
            this.reason = reason;
            // resolve里面将所有失败的回调拿出来执行
            while (this.onRejectedCallbacks.length) {
                this.onRejectedCallbacks.shift()(reason)
            }
        }
    }   
    // 并传入resolve和reject方法    
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  

  then(onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

    //判断一下当前js执行环境
    const microTask = typeof window !== 'undefined' ? queueMicrotask : process.nextTick;
    const innerPromise = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () =>  {
        microTask(() => {
          try {
            // 获取成功回调函数的执行结果
            const nextPromise = realOnFulfilled(this.value);
            // 传入 resolvePromise 集中处理 nextPromise有可能仍是Promise 
            resolvePromise(innerPromise, nextPromise, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        })  
      }

      const rejectedMicrotask = () => { 
        microTask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const nextPromise = realOnRejected(this.reason);
            // 传入 resolvePromise 集中处理 nextPromise有可能仍是Promise 
            resolvePromise(innerPromise, nextPromise, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        }) 
      }
      // 判断状态
      if (this.status === FULFILLED) {
        fulfilledMicrotask() 
      } else if (this.status === REJECTED) { 
        rejectedMicrotask()
      } else if (this.status === PENDING) {
        // 等待
        // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
        // 等到执行成功失败函数的时候再传递
        this.onFulfilledCallbacks.push(fulfilledMicrotask);
        this.onRejectedCallbacks.push(rejectedMicrotask);
      }
    }) 
    
    return innerPromise;
  }

  // resolve 静态方法
  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter;
    }

    // 转成常规方式
    return new MyPromise(resolve =>  {
      resolve(parameter);
    });
  }

  // reject 静态方法
  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

function resolvePromise(innerPromise, nextPromise, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (innerPromise === nextPromise) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // 判断nextPromise是不是 MyPromise 实例对象
  if(nextPromise instanceof MyPromise) {
    // 执行 nextPromise,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    nextPromise.then(resolve, reject)
  } else{
    // 普通值
    resolve(nextPromise)
  }
}

那么Promise.all、race如何实现呢

我们知道Promise的all、race这两个api,传入的参数必须是一个数组,但是all是并行发送统一返回、race是看哪个promise状态率先改变,以下是实现方式:

两者的试下思路比较的简单,不同点在于all是并发但需要等到所有的子Promise成功后才返回,但是race是任一子Promise状态改变后则立即返回。all比较适合用来并行发送一些无依赖关系的异步操作、race适用于从多个异步操作获取一个数据源(可以最快拿到)。

PromiseA+.js 复制代码
MyPromise.prototype.all = (promises) => {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            reject('Type Error');
        }
        let result = [],
            count = 0
       
        if (promises.length === 0) resolve([]);
        //如果数组中有promise reject 则直接抛出异常
        promises.forEach(promise => {
            Promise.resolve(promise).then((data) => {
                result.push(data)
                ++count === promise.length && resolve(result)
            }).catch((error) => reject(error))
           
        });
    })
}


MyPromise.prototype.race = (promises) => {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            reject('Type Error');
        }
        if (promises.length === 0) resolve([]);
        promises.forEach(promise => {
            Promise.resolve(promise).then((data) => {
                resolve(data)
            }).catch((error) => reject(error))
        });
    })
}

结语

Promise的使用在前端开发者频率非常高,它的存在首先避免了回调地狱,在单线程的js中为开发提供了异步操作的有一种途径,内置丰富的api也让我们可以对业务场景进行封装使用,By the way, vue中的nextTick的底层实现也是Promise,只不过vue3中废弃了vue中的降级处理,不再兼容setimeout、MutationObserver这些降级处理(这也是利用了js的事件循环机制😁,下期再讲)

相关推荐
昨天;明天。今天。7 分钟前
案例-任务清单
前端·javascript·css
一丝晨光33 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
夜流冰34 分钟前
工具方法 - 面试中回答问题的技巧
面试·职场和发展
Front思35 分钟前
vue使用高德地图
javascript·vue.js·ecmascript
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦3 小时前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
神之王楠3 小时前
如何通过js加载css和html
javascript·css·html
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈