likes
comments
collection
share

十分钟!坤坤陪你彻底搞懂Promise

作者站长头像
站长
· 阅读数 15

一、基本介绍

  1. Promise 是承诺的意思,理解下来就是在一段时间内,等待一件事情的完成,在浏览器环境中,这个一段时间就是异步操作的表现。
  2. 区分同步和异步:当我们调用一个方法之后,需要等待这个操作返回结果,就是异步操作,否则为同步操作。

二、为什么需要Promise

1. 常见的异步操作

  • 宏任务:setTimeout、ajax、 脚本<script>、MessageChannel(消息通道)、ui渲染、DOM事件
  • 微任务:Promise.then(原生的Promise)、mutationObserver(h5提供的api)

2. 异步回调存在的问题

  • 回调地狱、恶魔金字塔:当需要做多个异步操作时会导致代码多层嵌套,并且代码不整洁、不易阅读
  • 并行结果:多个异步任务未分前后顺序执行时,需要等待任务都完成后才可继续执行后续逻辑时,无法并行执行任务节省时间

3. 关于 Promise 状态 🌿

这里我用🐔 你太美来介绍,方便大家记忆,将 Promise 当做一颗鸡蛋。 当坤坤还只是一颗蛋时,需要经历孵化的过程,他才能成为现在万众瞩目的爱坤

Promise 的状态一共有三种

  • pending:是正在孵化中
  • fulfilled:则是孵化成功了
  • rejected:则是孵化失败了

4. Promise 的注意事项 ⚠️

  1. Promise 的结果只有 fulfilled 或者 rejected。蛋最后只有孵化成功或者孵化失败
  2. 当抛出异常时,会走到 rejected 的状态。蛋孵化的过程中夭折了,自然就孵化失败了
  3. Promise 的状态一旦从 pending 更改后,就不可再改变。蛋孵化成小鸡后就不会再变成蛋了
  4. new Promise 时,executor 函数会立即执行
  5. resolve 和 reject 是两个函数,给用户使用,调用 resolve 状态变为成功,调用 reject 状态变为失败
  6. 每个 Promise 都需要一个 then 方法,then 接收两个回调函数,一个是成功回调一个是失败回调

三、Promise的基本使用

⚠️ 这里我会将平常使用的 promise 结构拆分,方便理解

1.导入自己实现的 promise

 const Promise = require('./testPromise'); // 导入自己的promise
 const executor = (resolve, reject) => {
   setTimeout(e=>{
       // 调用了成功的回调
       resolve('小黑子露出鸡脚了吧~')
       // 调用了失败的回调
       // reject('小鸡子露出黑脚了吧~')
   }, 10);
 };

2. 构建 Promise 实例

构建后,在constructor中会立即执行 executor 函数

  const promise1 = new Promise(executor);

3. then 中的成功和失败回调

 const onFulfilled = data => {
   console.log(data, '孵化成功了~')
 };

 const onRejected = err => {
   console.log(err, '孵化失败了~')
 }

4. 调用执行

  promise1.then(onFulfilled, onRejected)

四、创建一个 Promise 类

⚠️ 这里的 executor 参数就是我们上面传入的方法,并且会立即执行

// 设置三个状态
  const PENDING = 'PENDING';
  const FULFILLED = 'FULFILLED';
  const REJECTED = 'REJECTED';

  class Promise {

    constructor(executor) {
       // DDDD 在这里做逻辑处理


    }

  }
  module.exports = Promise;

五、在Promise类中写入处理状态的逻辑

  • status:默认状态为 pending
  • value:正确回调返回的值
  • errorReason:失败回调的原因
  • resolvedCallbacks: 存放 then 中成功回调的队列
  • rejectedCallbacks: 存放 then 中失败回调的队列

⚠️ 注意事项

  1. 用户传入的executor方法,可能会存在异常错误,这里需要 try Catch 处理,发生错误后直接进入reject,异常错误则作为失败原因

  2. 这里的forEach(fn => fn())作用是,当传入的executor为一个异步操作时,这时的状态会一直为pending,所以要先将成功的回调和失败的回调存起来,等待状态变更后,再循环执行,使用了发布-订阅模式

  3. resolvePromise 方法相较复杂,放后边单独分析

// DDDD后期处理Promise使用,先在这打个标
function resolvePromise(promise2, x, resolve, reject) {};

// 实现Promise类
class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = null;
    this.errorReason = null;
    this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];

    // 用户调用的成功时的处理函数
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED
        this.resolvedCallbacks.forEach(fn => fn());
      }
    }

    // 用户调用的失败时的处理函数
    const reject = (error) => {
      if (this.status === PENDING) {
        this.errorReason = error
        this.status = REJECTED
        this.rejectedCallbacks.forEach(fn => fn());
      }
    }

    // 当执行器出现异常时,直接进入reject
    try {
      executor(resolve, reject)
    } catch (error) {
      // 这个异常就作为失败的原因
      reject(error)
    }
  }
  
  then(onFulfilled, onRejected) { }
}

六、实现 then 方法

1. then方法代码

then(onFulfilled, onRejected) {
    // 给onFulfilled和onRejected添加默认值
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err };
    
   let promise2 = new Promise((resolve, reject) => {
   
      // 处理resolve状态的逻辑
      if (this.status === FULFILLED) {};

      // 处理reject状态的逻辑
      if (this.status === REJECTED) {};

      // 处理pending状态的逻辑
       if (this.status === PENDING) {};
       
    });

    return promise2;
}

💡知识点

  • 每次都reutrn一个Promise, 在then中不停的new Promise实现链式调用,并且解决promise的回调陷阱问题
  • 每个状态判断中,使用setTimeout做成异步操作,是为了确保能拿到promise2,若直接使用promise2,上下文还没有执行完,promise2的结果为undefined
  • 每个状态判断中,若执行对应的回调时发生异常,使用try Catch捕获,并且执行promise2的reject
  • 链式调用时,只要中其中一个then的回调有返回值,x就会有值,否则为undefined
  • 如果onFulfilled或者onRejected为function,则使用传入进来的方法,否则使用默认的
  • onFulfilled中的:v => v 相当于 function (v) { return v}
  • onRejected中的:err => { throw err } 相当于 function (err) { return throw err}

十分钟!坤坤陪你彻底搞懂Promise


2. ✅处理fulfilled状态

if (this.status === FULFILLED) {
    setTimeout(() => {
       try {
           let x = onFulfilled(this.value);
           resolvePromise(promise2, x, resolve, reject);
       } catch (e) {
           reject(e)
       }
     })
};

3. ❎处理rejected状态

if (this.status === REJECTED) {
  setTimeout(() => {
       try {
           let x = onRejected(this.errorReason)
           resolvePromise(promise2, x, resolve, reject);
       } catch (e) {
           reject(e)
       }
   })
};

4. 💭处理pending状态

⚠️ 注意事项

  1. Promise此时的状态还未确认,在此状态中订阅,将成功回调存入到resolvedCallbacks失败回调都存入到rejectedCallbacks
  2. 这里需要存入一个可执行的函数到对应的数组中,函数的内容与其它两个判断的操作一样
if (this.status === PENDING) {

   this.resolvedCallbacks.push(() => {
       setTimeout(() => {
           try {
               let x = onFulfilled(this.value)
               resolvePromise(promise2, x, resolve, reject)
           } catch (e) {
               reject(e)
           }
       })
   });

   this.rejectedCallbacks.push(() => {
       setTimeout(() => {
           try {
               let x = onRejected(this.errorReason)
               resolvePromise(promise2, x, resolve, reject)
           } catch (e) {
               reject(e)
           }
       })
   });
   
}

七、实现resolvePromise方法

1. promise2 === x的作用

  • 主要是防止在then中return当前的promise2
  • 下面这种使用,正常情况下返回的promise2不会成功也不会失败,加了promise2 === x判断让他直接走失败
function resolvePromise(promise2, x, resolve, reject) {
     if(promise2 === x){
        const error = '[出现错误: 检测到Promise链接循环 #<Promise>]';
        return reject(new TypeError(error));
     }
}
// 调用例子
const promise2 = new Promise((resolve,reject)=>{
    resolve('鸡你太美');
}).then(data=>{
    return promise2;
});

2. 判断x是普通值还是Promise

  • 因为所有Promise都是按照Promises/A+规范实现,所以两个不同的Promise实例对象是可以相互组合使用。
  • 所以这里应该要判断x的类型是为object还是function
  • 如果使用x instanceof Promise来做判断,不是同一实例的Promise则会被当做普通值进入resolve
  • 当x为普通值时,可直接resolve(x)
function resolvePromise(promise2, x, resolve, reject) {
     if(promise2 === x){
        const error = '[出现错误: 检测到Promise链接循环 #<Promise>]';
        return reject(new TypeError(error));
     }
     
     if(x !== null && ['object', 'function'].includes(typeof x)) {
       // 处理当x为Promise时
       
     }else{
        // 普通值直接成功即可
        resolve(x);
     }
}

3. 当x不是普通值的时候

if(x !== null && ['object', 'function'].includes(typeof x)) {
  let called = false;
    try{
      let then = x.then; // 取出then,下面使用
      if(typeof then === 'function') {
          // 返回的promise成功时调用
          function success(data) {
              if(called) return;
              called = true;
              // 递归解析直到是普通值为止
              resolvePromise(promise2, data, resolve, reject);
          };

          // 返回失败时调用
          function error(err) {
              if(called) return;
              called = true;
              reject(err)
          };

          /**
          * 直接采用上一次的取出来的then方法
          * 使用x.then会重新取值,导致触发get
          */ 
          then.call(x, success, error);
      } else {
          // 进来这,说明then就是一个对象而已
          resolve(x); 
      }
    } catch(e){
        if(called) return;
        called = true;
        reject(e); // 取then的时候报错意味着出错了
    }
} else {
  resolve(x); // 普通值直接成功即可
}

4. 递归解析使用的场景

// 多层Promise的调用
new Promise((resolve, reject) => {
    resolve();
}).then(() => {
    return new Promise((resolve1, reject1) => {
        setTimeout(() => {
            const newP = new Promise((resolve2, reject2) => {
                setTimeout(() => resolve2('🐔你太美'), 1000)
            });
            resolve1(newP);
        }, 300)
    })
}).then(data => {
    console.log(data)
})

5. 其它Promise相互使用的场景

  • 下面这种使用一个不是通过自己的Promise创建的
  • 如果使用x instanceof Promise , 则会被当做普通值
// 使用一个不是通过new Promise创建的
let otherPromise = {
    then(onFulfilled,onRejected){
        onFulfilled()
    }
}
let p = new Promise((resolve)=>{
    resolve()
})
let p2 = p.then(()=>{
    return otherPromise
})
p2.then((s)=>{
    console.log('成功', s)
},(err)=>{
    console.log('失败', err)
})

八、测试Promise,并通过Promises/A+规范的验证

1. 写入deferred方法

默认测试的时候会调用此方法,会检测这个方法返回的对象是否符合规范,这个对象上需要有promise实例及resolvereject方法,在你完成的Promise.js中写入下面的方法

Promise.deferred = function () {
    let dfd = {}
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

2. 安装依赖

npm install promises-aplus-tests -g

3. 运行命令开始测试

  • filename = 最终完成的Promise.js名称
  • 然后就可以开心的测起来啦~
promises-aplus-tests 'filename'

4. 测试全部通过

十分钟!坤坤陪你彻底搞懂Promise

九、总结

  1. x为object或者function时,还需要判断x.then的类型是否为function,x才有可能是一个Promise
  2. x为object时,当做一个普通值进入resolve(x)
  3. called是用来判断是否调用了resolvePromise,调用过一次后就不再进入,防止重复调用
  4. 如果resolve的值一直是一个Promise,则一直递归解析,直到是普通值为止
  5. 其它的总结都写在了上面对应的地方
  6. Promise其它的静态方法后面再单独写一篇

十分钟!坤坤陪你彻底搞懂Promise

完整Promise代码

参考资料

公众号:前端悟道

转载自:https://juejin.cn/post/7202633255043088439
评论
请登录