【进阶 第8期】Promise 原理

我心飞翔 分类:javascript

【进阶 第8期】Promise 原理

前言

提到Promise,我们脑海可能第一时间想到的关键词是javaScript异步编程,熟悉又很陌生,promise能做什么?解决了什么问题?实现原理是什么?

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

基本用法

1、实例化

语法:new Promise((resolove, reject) => {...})

new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(data);
  } else {
    reject(error);
  }
});
 

2、Promise.prototype.then 方法

语法:promise.then(onFulfilled, onRejected)

promise.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2)
 

3、Promise.prototype.catch 方法

语法:promise.catch(onFulfilled, onRejected)

new Promise((resolve,reject){
    throw new Error('test')
}).catch(error=>{
    console.log(error)
})
 

3、all、resolve、reject、race等静态方法

  • Promise.all
  • Promise.resolve
  • Promise.reject
  • Promise.race
// resolve、reject 相当于只是定义了一个有状态的Promise
Promise.resolve('成功结果').then(r=>{console.log(r)}) 
Promise.reject('失败结果').catch(err=>{console.log(err)})  
// p1,p2,p3都成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值
Promise.all([p1,p2,p3]).then([r1,r2,r2] => {},err=>{console.log(err)})

// p1,p2,p3哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
Promise.race([p1,p2,p3]).then(res=>{console.log(res)},err=>{console.log(err)})
 

实现原理

回调地狱

在解释回调地狱之前,先来复习两个概念回调函数和: 同步vs异步

回调函数
当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。

异步任务
与之相对应的概念是“同步任务”。同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。

由于异步任务执行顺序不确定性,简单理解就是异步任务的回调函数执行不确定,因此为了保证回调函数执行顺序按照我们预期的结果来执行,平时coding工作中,我们经常大量使用的异步回调往往会发展成这个样子,举一个经典的ajax请求例子:

请求1(function(请求结果1){
    请求2(function(请求结果2){
        请求3(function(请求结果3){
            请求4(function(请求结果4){
                请求5(function(请求结果5){
                        ...
                })
            })
        })
    })
})

 

发现这段代码回调函数写的太多了,回调函数层层嵌套,造成代码可读性非常差,后期也不好维护

寻求解决方案

为了解决可读性以及可维护性问题,我们可以做一些设想。

1、使用链式调用

new Promise(请求1)
    .then(请求2(请求结果1))
    .then(请求3(请求结果2))
    .then(请求4(请求结果3))
    .then(请求5(请求结果4))
    .catch(处理异常(异常信息))
 

2、将异步转为接近同步代码的写法

let 请求结果1 = 请求1();
let 请求结果2 = 请求2(请求结果1); 
let 请求结果3 = 请求3(请求结果2); 
let 请求结果4 = 请求2(请求结果3); 
let 请求结果5 = 请求3(请求结果4); 
 

实现原理(核心逻辑实现)

1、三种状态

  • Pending(进行中)
  • Fulfilled(已成功)
  • Rejected(已失败)

2、状态转换

  • 只能有以下两种转换,Pending => Fulfilled 或 Pending => Rejected
  • 对象的状态不受外界影响
  • 状态改变之后不会再发生变化,会一直保持这个状态。

3、通过回调函数resolve 和 reject传递值

具体构造函数、实例方法、静态方法

4.1、构造函数

  • 构造函数Promise必须接受一个函数handle作为参数
  • 参数又包含两个函数 resolve 、reject

4.2、then 方法

  • 接受两个参数,成功时回调onFulfilled, 失败时回调onRejected,
  • 支持链式调用(then方法必返回一个promise对象)

4.3、all、resolve、reject、race

缺陷

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消
  • 内部错误无法反应到外部

手把手撸 —— 实现一个Promise 的 polyfill

第一步 定义三个状态

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
 

第二步 构造函数

  • 传入fn,fn接收两个参数(resolve,reject)作为状态传递
class MPromise {
    constructor(fn) {
        // 初始化默认状态
        this.status = PENDING
        this.value = undefined
        
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
        
        // 执行fn  【resolve/reject 为伪代码】
        // try catch 捕获执行中异常
        try {
          fn(resolve, reject);
        } catch (e) {
          reject(e);
        }
    }
}

 

第三步 resolve,reject回调函数

// 状态变更后调用resolve 或reject 分别处理成功、失败队列

class MPromise {
  constructor(fn) {
    const self = this;
    this.value = null;
    this.error = null;
    this.status = PENDING;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    function resolve(value) {
      if (value instanceof MPromise) {
        return value.then(resolve, reject);
      }
      if (self.status === PENDING) {
        setTimeout(() => {
          self.status = FULFILLED;
          self.value = value;
          self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
        }, 0);
      }
    }

    function reject(error) {
      if (self.status === PENDING) {
        setTimeout(function () {
          self.status = REJECTED;
          self.error = error;
          self.onRejectedCallbacks.forEach((callback) => callback(self.error));
        }, 0);
      }
    }
    try {
      fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
}
 

第四步 then 实现

1、如果当前状态已经转变为 FULFILLED 或 REJECTED,直接执行onFulfilled 或 onRejected的回调
2、将onFulfilled 和 onRejected,分别加入FULFILLED 和 REJECTED 回调队列管理。
3、由于then支持链式回调,返回值必须是MPromise实例

  then(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (error) => {
            throw error;
          };
    if (self.status === FULFILLED) {
      return new MPromise((resolve, reject)=>{
          try{
            resove(onFulfilled(self.value));
          }catch(e){
            reject(e);
          }
      })
    }
    if (self.status === REJECTED) {
       return new MPromise((resolve, reject)=>{
          try{
            reject(onRejected(self.error));
          }catch(e){
            reject(e);
          }
      })
    }
    if (self.status === PENDING) {
      return new MPromise((resolve, reject) => {
        self.onFulfilledCallbacks.push(onFulfilled);
        self.onRejectedCallbacks.push(onRejected);
      });
    }
  }
 

第五步 支持串行异步任务

以上代码中then目前支持同步任务。而用promise主要用来解决一组流程化的异步操作,因此需要把回调函数统一promiseify,以便于衔接各个回调函数(同步或异步)的处理结果

其它API 实现

第七步 验证是否符合promise/A+标准

Promise 的升级

ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致

拓展

  • promise 对象
  • promisesA+规范
  • 从零开始写一个符合Promises/A+规范的promise

回复

我来回复
  • 暂无回复内容