Promises/A+规范的代码实现

279 阅读7分钟

前置知识

  • 展开运算符 剩余运算符
  • 观察者模式,发布订阅模式
  • 柯里化
  • Promises/A+规范

展开运算符 剩余运算符

剩余运算符的官方传送门

展开语法的官方传送门

展开运算符,剩余运算符的小结

展开运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值,等号右边,或者放在实参上

rest01(...arr)
// 字符串数组化
var str='loycoder';
var arr3= [...str];

剩余运算符也是三个点号,不过其功能与扩展运算符恰好相反,把逗号隔开的值序列组合成一个数组,在等号左边,或者放在形参上。

function rest01(...arr) {}

var [a,...temp]=[1, 2, 4];

观察者模式,发布订阅模式

当问到大多数面试者两种模式大都可以侃侃而谈。 这里推荐一篇关于两者异同的好文。 Observer vs Pub-Sub pattern

面试回答亮点

  • 观察者模式大多数是以同步方式实现的,当某个事件发生时,被观察者调用其所有观察者的适当方法。发发布订阅则大都使用于异步方式(使用消息队列)。

  • 发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker

  • 观察者模式,多用于单个应用内部

  • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

柯里化和反柯里化

柯里化和反柯里化是高阶函数的一种应用,随着对源码的深入不再停留在苍白的概念层面。 函数可以作为参数传递到函数中,这个作为参数的函数叫回调函数,而拥有这个参数的函数就是高阶函数,回调函数在高阶函数中调用并传递相应的参数,这种函数式编程的方式,在高阶函数执行时,由于回调函数的内部逻辑不同,高阶函数的执行结果也不同,非常灵活。

// 通用的柯里化
const add = (a, b, c, d, e) => {
  return a + b + c + d + e;
};
const curring = (fn,arr = [])=>{
    let len = fn.length
    return (...args)=>{
        arr = arr.concat(args); // [1]  [1,2,3] < 5
        if(arr.length < len){
            return curring(fn,arr)
        }
        return fn(...arr)
    }
}
let r = curring(add)(1)(2)(3)(4)(5); // [1,2,3,4,5]
console.log(r);

这事说来话长,但也是进阶必备技能。打算在中间件一文中展开讨论。

入门可以看大神的文章 JS中的柯里化(currying)

Promises/A+规范

Promises/A+规范

Promises/A+规范的代码实现

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
const resolvePromise = (promise2, x, resolve, reject) => {
  //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2
  // 不能自己等待自己完成
  if(promise2 === x){ 
    return reject(new TypeError(`Chaining cycle detected for promise #<Promise>`));
  }
  // 这里判断是否是promise的方法是判断是否具有then方法
  if((typeof x === 'object' && x!==null) || typeof x ==='function'){
    //called变量主要是用来判断如果resolvePromise函数已经resolve或者reject了,那就不需要在执行下面的resolce或者reject。
    let called; 
    try{
      let then = x.then; // 取then的时候 可能会发生异常 getter
      if(typeof then === 'function'){  // 如果是函数
        //用call的原因是因为then方法里面this指向的问题。
        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)
        })
      }else{ 
        //如果是个普通对象就直接返回resolve作为结果
        resolve(x); 
      }
    }catch(e){
      if(called) return; 
      called = true
      reject(e);
    }
  }else{
    // 不是promise
    resolve(x); // 直接将结果抛出即可
  }
};
class Promise {
  constructor(executor) { // executor执行器,包含两个参数,分别是resolve和reject,new Promise这个executor就会执行
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.status = PENDING;
    this.onResolvedCallbacks = [];//定义存放成功的回调的数组
    this.onRejectedCallbacks = [];//定义存放失败回调的数组
    //此处应用了发布订阅模式,状态确认时把订阅的都执行一下
    let resolve = value => { // 成功
      if (this.status === PENDING) { // 只有当状态是pending的时候
        this.value = value; // 将值保存起来
        this.status = FULFILLED;
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };
    let reject = reason => { // 失败
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };
    try {
      executor(resolve, reject); // 出错了 直接退出即可
    } catch (e) {
      reject(e);
    }
  }
  then(onFulfilled, onRejected) { // 两个回调可能是可选参数
    //解决onFufilled,onRejected没有传值的问题
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
    //因为错误的值要让后面访问到,所以这里也要抛出个错误,不然会在之后then的resolve中捕获
    onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err};
    //因为在.then之后又是一个promise对象,所以这里肯定要返回一个promise对象。为了链式调用
    let promise2 = new Promise((resolve, reject) => { 
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            //难点在于onFulfilled的返回值有可能还是一个异步的Promise,需要等待其返回状态
            //如果不是,直接作为promise2的resolve结果
            let x = onFulfilled(this.value);
            //抽离出一个公共方法来判断他们是否为promise对象
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
        });
      }
    });
    return promise2;
  }
}
Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject
  })
  return dfd;
}
module.exports = Promise;

Promises 的其他方法

  • catch

    语法糖 then(null,errCallback)

  • finally

    在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数

  • all

    all 方法接受一个 promise 对象的数组,等数组中所有的 promise 对象的状态变为 fulfilled,然后返回结果,其结果也是一个数组,数组的每个值对应的是 promise 对象的内部结果。

  • race

    race 方法接受一个 promise 对象的数组,但是它只需要有一个 promise 变为 fulfilled 状态就会返回结果。

一些面试题

var promise = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 3000)
})

promise.then(2).then((n) => {
  console.log(n)
});

结果是1 ,因为then(2)非函数,会穿透到下一个then

Promise.resolve()
 .then(() => {
   return new Error('error!!!')
 })
 .then((res) => {
   console.log('then: ', res)
 })
 .catch((err) => {
   console.log('catch: ', err)
 })

其实就是抖了个机灵,打印then: Error: error!!! ,而非throw 所以不走catch分支

const promise = Promise.resolve()
.then(() => {
  return promise
})
 promise.catch(console.error)

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。参加源码

var p = new Promise(function(resolve, reject){
 resolve(1);
});
p.then(function(value){               //第一个then
 console.log(value);
 return value*2;
}).then(function(value){              //第二个then
 console.log(value);
}).then(function(value){              //第三个then
 console.log(value);
 return Promise.resolve('resolve'); 
}).then(function(value){              //第四个then
 console.log(value);
 return Promise.reject('reject');
}).then(function(value){              //第五个then
 console.log('resolve: '+ value);
}, function(err){
 console.log('reject: ' + err);
})

运行结果

1
2
undefined
"resolve"
"reject: reject"

这一题是对基础知识的一个总结

Promise对象的then方法返回一个新的Promise对象,因此可以通过链式调用then方法。 then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。

两个函数只会有一个被调用,函数的返回值将被用作创建then返回的Promise对象。 这两个参数的返回值可以是以下三种情况中的一种:

  • return 一个同步的值 ,或者 undefined(当没有返回一个有效值时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。
  • return 另一个 Promise,then方法将根据这个Promise的状态和值创建一个新的Promise对象返回。
  • throw 一个同步异常,then方法将返回一个rejected状态的Promise, 值是该异常。 根据以上分析,代码中第一个then会返回一个值为2(1*2),状态为resolved的Promise对象,于是第二个then输出的值是2。 第二个then中没有返回值,因此将返回默认的undefined,于是在第三个then中输出undefined。 第三个then和第四个then中分别返回一个状态是resolved的Promise和一个状态是rejected的Promise,依次由第四个then中成功的回调函数和第五个then中失败的回调函数处理。