Genluo / Precipitation-and-Summary

梳理体系化的知识点&沉淀日常开发和学习
MIT License
16 stars 1 forks source link

Promise规范解读 #14

Open Genluo opened 5 years ago

Genluo commented 5 years ago

Pomise在涉及模式中称之为揭示构造函数,因为生成的一个Promise的状态只会由内部函数决定,这里我们就不详细说规范了,这次就实现一个Promise类来学习:

function isFunction(fun) {
  return Object.prototype.toString.call(fun) === '[object Function]'
}

class Promise {
  constructor(fun) {
    if (!isFunction(fun)) {
      throw new TypeError(`Promise resolver ${fun} is not a function`);
    }
    this.fun = fun;
    this.state = 'pending'; // pending fulfilled rejected
    this.value = null;
    this.resolveList = [];
    this.rejectList = [];
    try {
      this.fun(this.resolve.bind(this), this.reject.bind(this));
    } catch(e) {
      this.reject(e);
    }
  }

  resolve(value) {
    if (this.state === 'pending') {
      this.state = 'fulfilled';
      this.value = value;
    }
    // 调用注册的回调函数
    this.resolveList.map(fun => {
      fun(value);
    })
  }

  reject(value) {
    if (this.state === 'pending') {
      this.state = 'rejected';
      this.value = value;
    }
    // 调用注册的回调函数
    this.rejectList.map(fun => {
      fun(value);
    })
  }

  then(onFulfilled, onRejected) {
    // 分两种情况,一种是状态已经改变,另一种是状态未改变
    const that = this;
    // fulfilled 状态
    if (this.state === 'fulfilled') {
      return new Promise((resolve, reject) => {
        try {
          const data  = isFunction(onFulfilled) ? onFulfilled(that.value) : that.value;
          if (data instanceof Promise) {
            // 如何函数返回一个promise,将当前Promise状态委托给返回的Promise
            return data.then(resolve, reject);
          }
          resolve(data);
        } catch(e) {
          reject(e);
        }
      })
    }
    // rejected 状态
    if(this.state === 'rejected') {
      return new Promise((resovle, reject) => {
        try {
          if (!isFunction(onRejected)) {
            // 不是函数直接传递错误
            return reject(that.value);
          }
          const data = onRejected(that.value);
          if (data instanceof Promise) {
            return data.then(resovle, reject);
          }
          resovle(data);
        } catch(e) {
          reject(e);
        }
      });
    }
    // pending状态
    if (this.state === 'pending') {
      return new Promise((resolve, reject) => {
        // 处理第一个参数
        if (isFunction(onFulfilled)) {
          that.resolveList.push((value) => {
            try {
              const data  = isFunction(onFulfilled) ? onFulfilled(value) : value;
              if (data instanceof Promise) {
                return data.then(resolve, reject);
              }
              resolve(data);
            } catch(e) {
              reject(e);
            }
          });
        } else {
          that.resolveList.push((value) => {
            resolve(value);
          })
        }
        // 处理第二个参数
        if (isFunction(onRejected)) {
          that.rejectList.push((value) => {
            try {
              const data = onRejected(value);
              if (data instanceof Promise) {
                return data.then(resovle, reject);
              }
              resolve(data);
            } catch(e) {
              reject(e);
            }
          })
        } else {
          this.rejectList.push((value) => {
            reject(value);
          })
        }
      })
    }
    // 如果this.state 不是这三种状态之一,报差提醒
    throw new Error('please check function')
  }
}

别人的实现方式

try {
  module.exports = Promise
} catch (e) {}

function Promise(executor) {
  var self = this

  self.status = 'pending'
  self.onResolvedCallback = []
  self.onRejectedCallback = []

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'resolved'
        self.data = value
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    })
  }

  function reject(reason) {
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = reason
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason)
        }
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (reason) {
    reject(reason)
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') { //because x could resolved by a Promise Object
      x.then(function(v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject)
    } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') {
        then.call(x, function rs(y) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject)
        }, function rj(r) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
    return v
  }
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
    throw r
  }

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onResolved
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onRejected
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'pending') {
    // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (r) {
          reject(r)
        }
      })

      self.onRejectedCallback.push(function(reason) {
          try {
            var x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
    })
  }
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

Promise.deferred = Promise.defer = function() {
  var dfd = {}
  dfd.promise = new Promise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

测试

如何判断我们实现的Promise符合规范,可以使用脚本来验证,使用方式如下:

npm i -g promises-aplus-tests
promises-aplus-tests Promise.js

问题

bluebird的实现好像是node端首选process.nextTick其次是setImmediate 。 browser端首选MutationObserver其次是setTimeout

可以使用闭包来实现

主要从性能和编码舒适度进行考虑

使用throw new Error()会使得代码进行Catch块,这样动态更改作用域链,是耗费性能的,但是在编码舒适度上是很有好的,throw会特殊显示,new Error()不会特殊形式。

事实上,Bluebird和ES6 Promise都做了类似的处理,在Promise被reject但又没有callback时,把错误输出到控制台。

好文章及其对应的源码

Genluo commented 4 years ago

【问题】 如何停止一个Promise链,并且释放后面的引用?

使用Promsie.race来处理竞态

function request(url, options = {}, callback) {
  const fetchPromise = fetch(url, options)
    .then(response => response.json());
  let abort;
  const abortPromise = new Promise((resolve, reject) => {
    abort = () => {
      reject(Error('abort'));
    };
  });
  Promise.race([fetchPromise, abortPromise])
    .then(({ data }) => {
      callback(data);
    }).catch(() => { });
  return abort;
}
useEffect(() => {
  const abort = request('https://cnodejs.org/api/v1/topic/5433d5e4e737cbe96dcef312', {}, setData);
  return () => {
    abort();
  };
});