从0到1实现一个满足Promises/A+规范的Promise

从0到1实现一个满足Promises/A+规范的Promise

前言

Promise是每个前端必备的知识储备,Promise可以解决开发中常遇见的嵌套回调同步并发,以及多个异步处理错误的问题。我们可以用Promise包裹异步方法使它更加优雅,同时让异步实现同步执行的async-await也是基于Promise。理解Promise的原理可以减少我们使用时的一些疑惑,用起来更加得心应手。

编写前需要点亮的一些知识点

  • javascript基本知识(原型、闭包、类型判断…,这里就不展开详说了)
  • 什么是高阶函数?
  • 你知道什么是发布订阅吗?

高阶函数

一个函数返回另一个函数,或者一个函数的参数中有函数就叫做高阶函数。

在业务的开发中对于一些通用性比较强核心函数,本次业务开发中需要对其增加一些额外的逻辑,我们就可以在使用高阶函数进行包裹处理。

// 核心方法
function core(...args){
    // 代码省略....
    console.log('core',args);
    // 代码省略....
}
// 在不更改核心代码的情况下,给core函数增加一些额外的逻辑
Function.prototype.before = function (cb) {
    return (...args) =>{
        cb();
        this(...args);
    }
}
let newCore = core.before(()=>{
    console.log('before')
})
newCore('a','b');

通过高阶函数,我们实现了对函数的扩展。

发布订阅

发布订阅主要分成两个部分on和emit,其中on就是把一些函数维护到一个数组当中,emit就是让数组当中的方法依次执行,其中on(订阅)和emit(发布)并没有明显的关联。下面我们通过一个具体场景来进行说明。

现在需要分别读取两个文件,我需要在这两个文件都读取完之后再将它们的内容打印出来,那么我应该在什么时机去打印该内容?

你可能会想到用一个变量来做计数器,当计数器初始值2,当计数器为0的时候说明这两个文件都被读取完了,此时我们就可以进行相应的逻辑操作。

let fs = require('fs');
let person = {};

function after(time, callback) {
  return function() {
    if (--time == 0) {
      callback();
    }
  }
}

let cb = after(2, function() {
  console.log(person)
})

fs.readFile('./promise/callback/age.txt', 'utf-8', function (err, data) {
  person.age = data;
  cb()
})

fs.readFile('./promise/callback/name.txt', 'utf-8', function (err, data) {
  person.name = data;
  cb()
})

但是此时我们想做更多其他事情,我们可能就需要不断的定义新的after1,after2…以及cb1,cb2…,然后在readFile中不断调用。这样用起来就会显得比较麻烦,不够优雅。此时我们通过发布订阅模式就可以做到更加灵活,通过数组去管理我们的业务函数,on将函数放进数组,emit遍历数组,每次异步读取文件时,只调用一次emit方法即可。

let fs = require('fs');
let person = {};
let event = { 
  arr: [],
  on: function(fn) {
    this.arr.push(fn)
  },
  emit: function() {
    this.arr.forEach(item => item())
  }
}

event.on(function() {
  console.log("读取数据中...")
})

event.on(function() {
  if (Object.keys(person).length === 2) {
    console.log(person)
  }
})

fs.readFile('./promise/callback/age.txt', 'utf-8', function (err, data) {
  person.age = data;
  event.emit();
})

fs.readFile('./promise/callback/name.txt', 'utf-8', function (err, data) {
  person.name = data;
  event.emit();
})

实现一个简易的Promise

实现一个简易的Promise,首先我们需要分析Promise的基本用法,通过new一个Promise(执行函数),我们可以得到一个Promise的实例promise。其中执行函数中传入两个参数,调用这个两个参数,我们可以改变promise的状态,让promise的状态从等待状态到成功/失败状态。然后我们可以在promise的then方法中通过参入两个函数,可以对成功/失败执行相应的逻辑处理。

const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
const PENDING = 'pending'; // 等待
class Promise {
  constructor(executor) {
    this.status = PENDING; // promise默认的状态
    this.value - undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    const resolve = (value) => { // 成功resolve函数
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED; // 修改状态
      }
    }
    const reject = (reason) => { // 失败reject函数
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED; // 修改状态
      }
    }
    try {
      executor(resolve, reject); // 立即执行
    } catch(e) {
      reject(e)
    }
  }
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }
    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

通过创建一个Promise类,在其构造函数中传入一个执行器executor,将status(promise的状态),value(成功的值),reason(失败的原因)放到Promise的实例上去,定义成功函数resolve,和失败函数reject,由于Promise的状态一经改变就不能变,所以在成功失败函数内部需要对status的状态判断才会去,对status的值进行相应的更新,以及对value和reason的赋值,由于Promise执行器executor是立即执行的,所以我们在构造函数中就要让其执行,如果执行函数的时候发生了异常,用try catch捕捉执行reject。Promise的创建我们已经完成,现在我们要实现then方法,在Promise类内部新增一个then方法,该方法会传入两个回调函数,成功回调函数onFulfilled,失败回调函数onRejected,通过判断status当前的值,执行相应的回调函数即可。

Promise异步问题

现在我们已经实现了一个简易的promise,但是要是我们在调用Promise的then方法的时候,status还未从pending转变为其他状态,那么该如何解决这个问题?

Promise.resolve(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(200)
  }, 3000)
})).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})

为解决该问题,我们需要在then当中对status等于pending时进行处理,这时候就需要用到发布订阅思想了,当调用then的时候如果此刻的状态status为pending,那么我们可以把当前回调函数放到相应的成功/失败回调的数组中存储起来,当状态从pending转变成fulfilled/rejected时,再将数组中的函数遍历执行。

const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
const PENDING = 'pending'; // 等待
class Promise {
  constructor(executor) {
   	// 代码省略...
    this.fulfilledCallback = []; // 成功回调
    this.rejectedCallback = []; // 失败回调
    const resolve = (value) => { // 成功resolve函数
      if (this.status === PENDING) {
        // 代码省略...
        this.fulfilledCallback.forEach(item => {
          item()
        });
      }
    }
    const reject = (reason) => { // 失败reject函数
      if (this.status === PENDING) {
       // 代码省略...
        this.rejectedCallback.forEach(item => {
          item()
        });
      }
    }
    // 代码省略...
  }
  // 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
  then(onFulfilled, onRejected) {
    if (this.status === PENDING) { // 说明then是异步的
      this.fulfilledCallback.push(() => { 
        onFulfilled(this.value);
      })
      this.rejectedCallback.push(() => {
        onRejected(this.reason);
      })
    }
    // 代码省略...
  }
}

灵魂then的链式调用

then的链式调用是Promise的灵魂,同时也是实现Promise最难的一部分。要实现then的链式调用,首先我们要对then的用法有个详细认知。

  1. Promise的链式调用,当调用then方法后会返回一个新的Promise
  2. 当then中方法无论是成功还是失败返回的是一个普通值,不是Promise的情况,会作为外层下一次then的成功结果。
  3. 当then中方法会执行出错,会走到外层下一次then的失败结果。
  4. 如果then中返回的是一个Promise对象,此时会根据Promise的结果来处理是走成功还是失败。

用一句话总结就是:如果返回一个普通值 (除了Promise) 就会传递给下一个then的成功,如果返回一个失败的Promise或者抛出异常,会走下一个then的失败

根据then链式的用法我们很容易就能写出当then中返回值是普通值的时候的情况。

// 代码省略...
class Promise {
  constructor(executor) {
    // 代码省略...
  }
  // 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
  then(onFulfilled, onRejected) {
    let promise = new Promise((resolve, reject) => {
      if (this.status === PENDING) { // 说明then是异步的
        this.fulfilledCallback.push(() => {
          try {
            let x = onFulfilled(this.value);
            resolve(x);
          } catch (error) {
            reject(error)
          }
        })
        this.rejectedCallback.push(() => {
          try {
            let x = onRejected(this.reason);
            resolve(x);
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === FULFILLED) {
        try {
          let x = onFulfilled(this.value);
          resolve(x);
        } catch (error) {
          reject(error)
        }
      }
      if (this.status === REJECTED) {
        try {
          let x = onRejected(this.reason);
          resolve(x);
        } catch (error) {
          reject(error)
        }
      }
    })
    return promise;
  }
}

用一个新的Promise将我们之前写在then中的逻辑包裹起来,然后将用变量x将回调函数的返回值存储起来,并执行promise的resolve(x),这过程中如果发生错误,利用trycatch捕获并执行promise的reject方法。最后再将promise返回即可。

解决了then中回调函数返回值是普通值的情况,现在我们需要来解决当返回值为Promise时的情况:

// 代码省略...
function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('错误'));
  }
  if (typeof x === 'function' || (x !== null && typeof x === 'object')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, res => {
          resolvePromise(promise, res, resolve, reject);
        }, rej => {
          reject(rej);
        })
      } else {
        resolve(x);
      }
    } catch (err) {
      reject(rej);
    }
  } else {
    resolve(x);
  }
}
class Promise {
  constructor(executor) {
   // 代码省略...
  }
  // 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
  then(onFulfilled, onRejected) {
    let promise = new Promise((resolve, reject) => {
      if (this.status === PENDING) { // 说明then是异步的
        this.fulfilledCallback.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
        this.rejectedCallback.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
      }
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);

      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);
      }
    })
    return promise;
  }
}

此时我们需要对回调函数的返回值x进行处理,如果是普通值的话我们直接调用promise的resolve即可。如果返回值x是Promise对象的话,那么我需要对Promise对象进行递归处理,当处理的值最终为普通值的时候,调用promise的resolve即可。这里我们可以把处理x的方法抽离出方法resolvePromise,分别传入promise,x,以及promise的resolve,reject四个参数,由于这里我们要使用promise,promise还没定义,所以我们需要用一个setTimeout让其异步。现在我们来多对resolvePromise进行处理,主要可以分成以下几个点:

  1. 判断promise === x ?是的话调用reject。
  2. 判断x是否是function或者x是否为object且不为null。
  3. 如果第2步为假则说明x是个普通值,直接调用resolve方法即可。
  4. 如果第2步为真则去判断x的then属性。
  5. 若then不为function则说明x是个普通值,直接调用resolve方法即可。
  6. 若then为function我们可以认为x就是Promise,调用then方法并使用call方法让其内部this指向x,在then的成功回调里调用resolvePromise,将第二参数替换成回调函数传入的参数即可。在then的失败回调里直接调用reject方法即可。

完成上述操作,我们基本就完成了一个符合Promises/A+规范的Promise啦~😊。

附上完整代码:

const FULFILLED = 'FULFILLED'; // 成功
const REJECTED = 'REJECTED'; // 失败
const PENDING = 'PENDING'; // 等待
function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('错误'));
  }
  if (typeof x === 'function' || (x !== null && typeof x === 'object')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, res => {
          resolvePromise(promise, res, resolve, reject);
        }, rej => {
          reject(rej);
        })
      } else {
        resolve(x);
      }
    } catch (err) {
      reject(rej);
    }
  } else {
    resolve(x);
  }
}
class Promise {
  constructor(executor) {
    this.status = PENDING; // promise默认的状态
    this.value - undefined; // 成功的原因
    this.reason = undefined; // 失败的原因
    this.fulfilledCallback = []; // 成功回调
    this.rejectedCallback = []; // 失败回调
    const resolve = (value) => { // 成功resolve函数
      if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED; // 修改状态
        this.fulfilledCallback.forEach(item => {
          item()
        });
      }
    }
    const reject = (reason) => { // 失败reject函数
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED; // 修改状态
        this.rejectedCallback.forEach(item => {
          item()
        })
      }
    }
    try {
      executor(resolve, reject); // 立即执行
    } catch (e) {
      reject(e)
    }
  }
  // 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
  then(onFulfilled, onRejected) {
    let promise = new Promise((resolve, reject) => {
      if (this.status === PENDING) { // 说明then是异步的
        this.fulfilledCallback.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
        this.rejectedCallback.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
      }
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);

      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0);
      }
    })
    return promise;
  }
}

module.exports = Promise
// 测试
let MyPromise = require('./source/4promise')
let promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功')
  }, 3000)
  console.log('开始')
})

promise.then((res) => {
  console.log(res)
  return new MyPromise((resolve, reject) => {
    reject('失败88888')
  })
}, err => {
  console.log(err)
}).then((res) => {
  console.log("---2res", res);
}, err => {
  console.log("---2err", err);
  return { ss: "jinhong" };
}).then(res => {
  console.log("-------3res", res);
}, err => {
  console.log("-------3err", err);
}).then(res => {
  console.log("-------4res", res);
}, err => {
  console.log("-------4err", err);
})

在这里插入图片描述

其他方法

Promise除了基本用法,还有Promise.resolve(value)Promise.reject(value)它们可以分别创建成功值/拒绝原因的Promise对象。以及Promise.all() Promise.race() 两个并行运行异步操作的组合式工具,以及错误捕获catch方法。这些方法在我们理解了then链式调用后,实现起来就相应的变的简单了起来。

  • Promise.resolve(value)Promise.reject(value)

    这两个静态方法直接new一个Promise并在执行函数中调用相应的成功,失败方法即可。

    // 代码省略...
    function resolvePromise(promise, x, resolve, reject) {
      // 代码省略...
    }
    class Promise {
      // 代码省略...
     	static resolve(value){
        return new Promise((resolve,reject)=>{
          resolve(value);
        })
      }
      static reject(value){
        return new Promise((resolve,reject)=>{
          reject(value);
        })
      }
    }
    
  • catch

    catch是Promise原型上的方法,catch实现主要是指定回调函数,将它的回调函数当作参数传递给then的失败回调即可。

    // 代码省略...
    function resolvePromise(promise, x, resolve, reject) {
      // 代码省略...
    }
    class Promise {
      // 代码省略...
      catch(errorFn){
        return this.then(null,errorFn);
      }
    }
    
  • finally

    无论成功失败都会执行finally,且可以继续向下执行。

    // 代码省略...
    function resolvePromise(promise, x, resolve, reject) {
      // 代码省略...
    }
    class Promise {
      // 代码省略...
      finally(cb) {
         return this.then((data) => {
            return Promise.resolve(cb()).then(() => data);
        }, (err) => {
            return Promise.resolve(cb()).then(() => { throw err });
        })
      }
    }
    
  • Promise.all()

    Promise.all接收一个数组等待所有都成功完成,返回一个新数组,在这当中如果有一个失败,那么 Promise.all 将立即变为失败。

    // 代码省略...
    function resolvePromise(promise, x, resolve, reject) {
      // 代码省略...
    }
    class Promise {
      // 代码省略...
      static all = function (promises) {
        return new Promise((resolve, reject) => {
          let result = [];
          let times = 0;
          const processSuccess = (index, val) => {
            result[index] = val;
            if (++times === promises.length) {
              resolve(result);
            }
          }
          for (let i = 0; i < promises.length; i++) {
            let p = promises[i];
            if (p && typeof p.then === 'function') {
              p.then((data) => {
                processSuccess(i, data)
              }, reject)
            } else {
              processSuccess(i, p);
            }
          }
        })
      }
    }
    
    Promise.all([ 
        new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('成功1')
        }, 2000);
    }), new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('成功2')
        }, 1000);
    }),1, 2, 3]).then(data => {
        console.log(data);
    }).catch(err => {
        console.log(err,'errr')
    })
    
  • Promise.race()

    Promise.race()谁最先成功或失败就采用他的结果,一般可以用来进行超时处理。

    // 代码省略...
    function resolvePromise(promise, x, resolve, reject) {
      // 代码省略...
    }
    class Promise {
      // 代码省略...
      static race = function (promises) {
        return new Promise((resolve, reject) => {
          for (let i = 0; i < promises.length; i++) {
            let p = promises[i];
            if (p && typeof p.then === 'function') {
              p.then(resolve, reject); // 一旦成功就直接 停止
            } else {
              resolve(p);
            }
          }
        })
      }
    }
    
    // 测试
    let p1 = new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve('成功')
        }, 1000);
    })
    
    let p2 = new Promise((resolve,reject)=>{
        setTimeout(() => {
            reject('失败')
        }, 500);
    })
    Promise.race([p1, p2, null, 1]).then((data) => {
        console.log(data);
    }).catch((err) => {
        console.log(err)
    });
    

应用

  • 图片加载超时,脚本超时加载超时问题,不再采用成功的结果

    var abort;
    let p1 = new Promise((resolve, reject) => {
      abort = reject;
      setTimeout(() => {
        resolve('成功');
      }, 3000);
    })
    p1.abort = abort;
    p1.then(data => {
      console.log(data);
    }, err => {
      console.log(err);
    })
    setTimeout(() => {
      p1.abort("超过一秒了");
    }, 1000);
    

    这样暴露一个变量出来显得比较的low,此时我们可以利用race的特性,去new一个超时的promise。

    function wrap(p) {
      let abort;
      let p1 = new Promise((resolve, reject) => {
        abort = reject;
      })
      let p2 = Promise.race([p, p1]);
      p2.abort = abort;
      return p2;
    }
    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功');
      }, 3000);
    })
    
    let p2 = wrap(p1);
    p2.then(data => {
      console.log(data);
    }, err => {
      console.log(err);
    })
    
    setTimeout(() => {
      p2.abort("超过一秒了");
    }, 1000);
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值