美文网首页
使用 ES6 实现一个简单的 Promise

使用 ES6 实现一个简单的 Promise

作者: wubai_01ec | 来源:发表于2019-01-06 14:02 被阅读0次

Promise 基本结构

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("FULFILLED");
  }, 1000);
});
p1.then(() => {}, () => {});

构造函数 Promise 必须接受一个函数作为参数,同时这个函数又包含 resolvereject 两个参数,这两个参数同样是函数。

Promise 对象存在三种状态,Pending(进行中)Fulfilled(已完成)Rejected(已完成)

resolve 的作用就是将 Promise 对象的状态从 Pending 转换为 Fulfilled。同时传递一个值出去。

reject 则是将 Promise 对象的状态从 Pending 转换为 Rejected。同时传递一个值(可以是错误)出去。

Promise 实例生成后,可以使用 then 指定 Fulfilled 状态和 Rejected 状态的回调函数。

我们定义一个函数,用来判断一个变量是否是函数——

const isFunction = variable => typeof variable === "function";

同时定义一个 MyPromise 的类——

const isFunction = variable => typeof variable === "function";

class MyPromise {
  constructor(fn) {
    if (!isFunction(fn)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
  }
  then(onFulfilled, onRejected) {}
}

定义 Promise 的三种状态,我们可以借助 ES6 的 Symbol 对象,来生成一个唯一标识的值——

const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

Symbol 生成的值必定是独一无二的,Symbol 函数的的参数仅仅作为控制台输出时的解释说明,并不会影响到 Symbol 的返回值的内容。

MyPromise 添加状态和值——

const isFunction = variable => typeof variable === "function";

const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

class MyPromise {
  constructor(fn) {
    if (!isFunction(fn)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
    this.status = PENDING;
    this.value = undefined;
  }
  then(onFulfilled, onRejected) {}
}

但是这样会出现一个问题,MyPromise 实例化后能够从类的外部获得 statusvalue,并且能够从外界进行篡改。一般来说,应该将这个值设置为私有属性。

ES6 中 Class 并不支持私有属性,虽然有一个提案是使用 # 前缀来表示这个值为私有属性,但是有些还不能支持(虽然 babel 已经支持了)

设置私有属性有几种方法——

  1. 使用 # ,但是在没有 Babel 的情况下无法使用
class MyClass {
  #x;
  constructor(v) {
    #x = v;
  }
}
  1. 在类外声明一个对象,用对象存储属性,一般使用 ES6 的 WeakMap ,因为 WeakMap 的键名所引用的对象都是弱引用,即垃圾回收机制不会将该引用考虑在内(引用计数垃圾收集,不会将存储在 WeakMap 中的内容认为是被 WeakMap 引用)(
const __ = new WeakMap();
function get(that, key) {
  try {
    return __.get(that)[key];
  } catch (e) {
    console.error(e);
    return undefined;
  }
}
function set(that, key, value) {
  const lastValue = __.get(that);
  const newValue = {
    ...lastValue,
    [key]: value
  };
  __.set(that, newValue);
}
class MyClass {
  constructor(v) {
    set(this, x, v); // this.x = v; 但是这个 x 是私有属性,外界无法得到
  }
  anyWay() {
    return get(this, x);
  }
}
  1. 借助 Symbol,类外因为无法拿到这个 Symbol 返回值所以无法使用该属性
const x = Symbol("x");
class MyClass {
  constructor(v) {
    this[x] = v;
  }
  anyWay() {
    return this[x];
  }
}

这里我们采取第二种方法

const isFunction = variable => typeof variable === "function";

const __ = new WeakMap();
function get(that, key) {
  try {
    return __.get(that)[key];
  } catch (e) {
    console.error(e);
    return undefined;
  }
}
function set(that, key, value) {
  const lastValue = __.get(that);
  const newValue = {
    ...lastValue,
    [key]: value
  };
  __.set(that, newValue);
}

const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

const promiseStatus = Symbol("status");
const promiseValue = Symbol("value");

class MyPromise {
  constructor(fn) {
    if (!isFunction(fn)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
    set(this, promiseStatus, PENDING);
    set(this, promiseValue, undefined);
  }
  then(onFulfilled, onRejected) {}
}

因为 then 有两个参数,onFulfilledonRejected,都是可选参数。

如果 onFulfilledonRejected 都不是函数,其必须被忽略。

如果 onFulfilled 是函数,当 Promise 状态变为成功时必定调用,且第一个参数应该是 Promise 成功状态 resolve(val) 是传入的值(即 value);在 Promise 状态改变前其不可被调用;被调用的次数不超过一次。

onRejected 同理。

then 方法可以被同一个 Promise 对象调用多次,而且所有的 onFulfilledonRejected 会按照注册顺序依次回调。

const p1 = new Promise(() => {});
p1.then(() => {});
p1.then(() => {});

then 必须返回一个新的 Promise,支持链式调用。

const p1 = new Promise(() => {});
const p2 = p1.then(() => {}).then(() => {});

Promise 的执行规则——

值的传递

如果 onFulfilled 或者 onRejected 返回一个 x——

  • xPromise,则后一个回调函数,就会等待该 Promise 对象的状态改变,才会被调用,并且新的 Promise 状态和 x 的状态相同。
  • x 不为 Promise,则将 x 直接作为新返回的 Promise 对象的传递出去的值,即新的 onFulfilled 或者 onRejected 的参数。
const p1 = new Promise(resolve => {
  setTimeout(() => {
    resolve();
  }, 1000);
});
p1.then(() => {
  return "普通值";
}).then(res => {
  console.log(res); // 普通值
});

p1.then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Promise");
    }, 1000);
  });
}).then(res => {
  console.log(res); // Promise
});

但如果在 onFulfilled 或者 onRejected 中抛出一个异常 e,则 then 返回的 Promise 的状态将变为 Rejected,并传递出 e

const p1 = new Promise(resolve => {
  setTimeout(() => {
    resolve("success");
  }, 1000);
})
  .then(() => {
    throw new Error("这里抛出一个异常");
  })
  .then(
    res => {
      console.log(res);
    },
    err => {
      console.log(err); // 这里抛出一个异常
    }
  );

因为可能出现【使用 then 绑定的 onFulfilledonRejected 的时候,Promise 的状态依旧是 Pending】的情况,所以我们需要使用两个数组去维护他们,同样借助 WeakMap 去维护他们

const isFunction = variable => typeof variable === "function";

const __ = new WeakMap();
function get(that, key) {
  try {
    return __.get(that)[key];
  } catch (e) {
    console.error(e);
    return undefined;
  }
}
function set(that, key, value) {
  const lastValue = __.get(that);
  const newValue = {
    ...lastValue,
    [key]: value
  };
  __.set(that, newValue);
}

const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

const promiseStatus = Symbol("status");
const promiseValue = Symbol("value");
const fulfilledCallbackQueue = Symbol("fulfilledCallbackQueue");
const rejectedCallbackQueue = Symbol("rejectedCallbackQueue");

const resolve = Symbol("resolve");
const reject = Symbol("reject");

class MyPromise {
  constructor(fn) {
    if (!isFunction(fn)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
    set(this, promiseStatus, PENDING);
    set(this, promiseValue, undefined);
    set(this, fulfilledCallbackQueue, []);
    set(this, rejectedCallbackQueue, []);
    try {
      fn(this[resolve].bind(this), this[reject].bind(this));
    } catch (e) {
      this[reject](e);
    }
  }

  [resolve](val) {}
  [reject](err) {}

  then(onFulfilled, onRejected) {}
}

因为实例化 Promise 时传入的回调函数包含 resolvereject ,这两个应该在 Promise 中自己实现,并且在 Promise 的构造函数中调用这个回调函数并且传入 resolvereject

这里就涉及到 ES6 中 Class 构建私有方法的问题,一般来说有以下几种方法——

  1. 命名上区分,即在命名前面加一个 _ 表示这应该是一个私有方法,而且不希望外界能够调用这个方法
class MyClass {
  _anyWay() {}
}

但其实类的外部依旧可以调用这个方法。

  1. 将私有方法移出类
function anyWay() {}

class MyClass {
  foo() {
    anyWay.call(this);
  }
}
  1. 借助 Symbol
const anyWay = Symbol("private func");

class MyClass {
  [anyWay]() {}
}

我们这里采取的是第 3 种方案

const isFunction = variable => typeof variable === "function";

const __ = new WeakMap();
function get(that, key) {
  try {
    return __.get(that)[key];
  } catch (e) {
    console.error(e);
    return undefined;
  }
}
function set(that, key, value) {
  const lastValue = __.get(that);
  const newValue = {
    ...lastValue,
    [key]: value
  };
  __.set(that, newValue);
}

const promiseStatus = Symbol("status");
const promiseValue = Symbol("value");
const fulfilledCallbackQueue = Symbol("fulfilledCallbackQueue");
const rejectedCallbackQueue = Symbol("rejectedCallbackQueue");

const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

const resolve = Symbol("resolve");
const reject = Symbol("reject");

class MyPromise {
  constructor(fn) {
    if (!isFunction(fn)) {
      throw new Error("MyPromise must accept a function as a parameter");
    }
    set(this, promiseStatus, PENDING);
    set(this, promiseValue, undefined);
    set(this, fulfilledCallbackQueue, []);
    set(this, rejectedCallbackQueue, []);
    try {
      fn(this[resolve].bind(this), this[reject].bind(this));
    } catch (e) {
      this[reject](e);
    }
  }

  [resolve](val) {
    const run = () => {
      if (get(this, promiseStatus) !== PENDING) return;
      const runFulfilled = value => {
        let cb;
        while ((cb = get(this, fulfilledCallbackQueue).shift())) {
          cb(value);
        }
      };
      const runRejected = error => {
        let cb;
        while ((cb = get(this, rejectedCallbackQueue).shift())) {
          cb(error);
        }
      };
      if (val instanceof MyPromise) {
        val.then(
          value => {
            set(this, promiseValue, value);
            set(this, promiseStatus, FULFILLED);
            runFulfilled(value);
          },
          err => {
            set(this, promiseValue, err);
            set(this, promiseStatus, REJECTED);
            runRejected(err);
          }
        );
      } else {
        set(this, promiseValue, val);
        set(this, promiseStatus, FULFILLED);
        runFulfilled(val);
      }
    };
    setTimeout(run, 0);
  }
  [reject](err) {
    if (get(this, promiseStatus) !== PENDING) return;
    const run = () => {
      set(this, promiseStatus, REJECTED);
      set(this, promiseValue, err);
      let cb;
      while ((cb = get(this, rejectedCallbackQueue).shift())) {
        cb(err);
      }
    };
    setTimeout(run, 0);
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      const fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value);
          } else {
            const res = onFulfilled(value);
            if (res instanceof MyPromise) {
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          onRejectedNext(err);
        }
      };
      const rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error);
          } else {
            const res = onRejected(error);
            if (res instanceof MyPromise) {
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          onRejectedNext(err);
        }
      };
      switch (get(this, promiseStatus)) {
        case PENDING:
          const newFulfilledCallbackQueue = [
            ...get(this, fulfilledCallbackQueue),
            fulfilled
          ];
          set(this, fulfilledCallbackQueue, newFulfilledCallbackQueue);
          const newRejectedCallbackQueue = [
            ...get(this, rejectedCallbackQueue),
            rejected
          ];
          set(this, rejectedCallbackQueue, newRejectedCallbackQueue);
          break;
        case FULFILLED:
          fulfilled(get(this, promiseValue));
          break;
        case REJECTED:
          rejected(get(this, promiseValue));
          break;
      }
    });
  }
}

相关文章

网友评论

      本文标题:使用 ES6 实现一个简单的 Promise

      本文链接:https://www.haomeiwen.com/subject/tirsrqtx.html