Promise 基本结构
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("FULFILLED");
}, 1000);
});
p1.then(() => {}, () => {});
构造函数 Promise 必须接受一个函数作为参数,同时这个函数又包含 resolve 和 reject 两个参数,这两个参数同样是函数。
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 实例化后能够从类的外部获得 status 和 value,并且能够从外界进行篡改。一般来说,应该将这个值设置为私有属性。
ES6 中 Class 并不支持私有属性,虽然有一个提案是使用 # 前缀来表示这个值为私有属性,但是有些还不能支持(虽然 babel 已经支持了)
设置私有属性有几种方法——
- 使用 # ,但是在没有 Babel 的情况下无法使用
class MyClass {
#x;
constructor(v) {
#x = v;
}
}
- 在类外声明一个对象,用对象存储属性,一般使用 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);
}
}
- 借助
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 有两个参数,onFulfilled 和 onRejected,都是可选参数。
如果 onFulfilled 和 onRejected 都不是函数,其必须被忽略。
如果 onFulfilled 是函数,当 Promise 状态变为成功时必定调用,且第一个参数应该是 Promise 成功状态 resolve(val) 是传入的值(即 value);在 Promise 状态改变前其不可被调用;被调用的次数不超过一次。
onRejected 同理。
then 方法可以被同一个 Promise 对象调用多次,而且所有的 onFulfilled 或 onRejected 会按照注册顺序依次回调。
const p1 = new Promise(() => {});
p1.then(() => {});
p1.then(() => {});
then 必须返回一个新的 Promise,支持链式调用。
const p1 = new Promise(() => {});
const p2 = p1.then(() => {}).then(() => {});
Promise 的执行规则——
值的传递
如果 onFulfilled 或者 onRejected 返回一个 x——
-
x为Promise,则后一个回调函数,就会等待该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 绑定的 onFulfilled 和 onRejected 的时候,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 时传入的回调函数包含 resolve 和 reject ,这两个应该在 Promise 中自己实现,并且在 Promise 的构造函数中调用这个回调函数并且传入 resolve 和 reject。
这里就涉及到 ES6 中 Class 构建私有方法的问题,一般来说有以下几种方法——
- 命名上区分,即在命名前面加一个
_表示这应该是一个私有方法,而且不希望外界能够调用这个方法
class MyClass {
_anyWay() {}
}
但其实类的外部依旧可以调用这个方法。
- 将私有方法移出类
function anyWay() {}
class MyClass {
foo() {
anyWay.call(this);
}
}
- 借助
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;
}
});
}
}