Promise
1-3 纯属废话 = =
1. 术语
promise是一个具有 then 方法的对象或者函数。thenable是一个定义了 then 方法的对象或者函数。value是一个合法的 JavaScript 值(包括 undefined、thenable 或 promise)。exception是一个使用 throw 语句抛出的值。reason是一个值,其表明了 promise 被拒绝的原因。
2. 要求
2.1 Promise 的状态
Promise 必须处于以下三种状态之一:pending, fulfilled, or rejected.
- when pending -> fulfilled or rejected
- when fulfilled 不能变更状态,并且具有一个值,其不能被改变。
- when rejected 不能变更状态,并且具有一个被拒绝的理由,其不能被改变。
2.2 then 方法
承诺必须提供 then 方法来访问其当前或最终值或原因。
一个 promise 的then方法 接受两个参数
promise.then(onFulfilled, onRejected);
2.2.1 onFulfilled 和 onRejected 都是可选参数
- 如果 onFulfilled 不是一个函数,那么它将被忽略。(替换为一个默认函数 (value) => value)
- 如果 onRejected 不是一个函数,那么它将被忽略。(替换为一个默认函数 (reason) => { throw reason; } )
2.2.2 如果 onFulfilled 是一个函数
- 必须在 promise 完成后调用它,promise 的值作为它的第一个参数。
- 在 promise 完成之前不能调用它。
- 它不能被多次调用。
2.2.3 如果 onRejected 是一个函数
- 必须在 promise 被拒绝后调用它,promise 的原因作为它的第一个参数。
- 在 promise 被拒绝之前不能调用它。
- 它不能被多次调用。
2.2.4 在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected
2.2.5 onFulfilled 和 onRejected 必须作为函数调用(即没有这个值)。
2.2.6 then 可以在同一个 promise 上多次调用。
- 如果 promise 完成时,所有相应的 onFulfilled 回调必须按照它们对 then 的原始调用的顺序执行。
- 如果 promise 被拒绝,则所有相应的 onRejected 回调必须按照它们对 then 的原始调用的顺序执行。
2.2.7 then 必须返回一个 新的 promise
promise2 = promise1.then(onFulfilled, onRejected);
- 如果 onFulfilled 或 onRejected 返回值 x,则运行 Promise Resolution Procedure [[Resolve]](promise2, x)。
- 如果 onFulfilled 或 onRejected 抛出异常 e,promise2 必须以 e 作为原因被拒绝。
- 如果 onFulfilled 不是函数并且 promise1 已实现,则 promise2 必须以与 promise1 相同的值实现。
- 如果 onRejected 不是函数并且 promise1 被拒绝,则 promise2 必须以与 promise1 相同的原因被拒绝。
2.3 The Promise Resolution Procedure
承诺解析过程是一个抽象操作,将承诺和值作为输入,我们将其表示为 [[Resolve]](promise, x)。 如果 x 是一个 thenable,它会尝试让 promise 采用 x 的状态,假设 x 的行为至少有点像 promise。 否则,它以值 x 履行承诺。对 thenables 的这种处理允许 promise 实现互操作,只要它们公开符合 PromisesA+ 的 then 方法。 它还允许 PromisesA+ 实现使用合理的 then 方法“同化”不一致的实现。
要运行 [[Resolve]](promise, x),请执行以下步骤:
2.3.1. 如果 promise 和 x 引用同一个对象,则以 TypeError 作为原因拒绝 promise。
2.3.2 如果 x 是一个 promise,采用它的状态:
- 如果 x 是 pending,promise 必须保持 pending 直到 x 被完成或拒绝。
- 如果当 x 被完成时,用相同的值实现承诺。
- 如果当 x 被拒绝时,以同样的理由拒绝承诺。
2.3.3 否则,如果 x 是一个对象或函数,
- 设 then 为 x.then。
- 如果检索属性 x.then 导致抛出异常 e,则以 e 为原因拒绝 promise。
- 如果 then 是一个函数,则使用 x 作为 this 调用它,第一个参数是 resolvePromise,第二个参数是 rejectPromise,其中:
- 如果使用值 y 调用 resolvePromise 时,则运行 [[Resolve]](promise, y)。
- 如果使用原因 r 调用 rejectPromise,则使用 r 拒绝 promise。
- 如果同时调用了 resolvePromise 和 rejectPromise,或者对同一个参数进行了多次调用,则第一个调用优先,任何进一步的调用都将被忽略。
- 如果调用后抛出异常 e,
- 如果调用了 resolvePromise 或 rejectPromise ,则忽略它。
- 否则,以 e 为理由拒绝 promise。
- 如果 then 不是函数,则用 x 实现 promise。
2.3.4 如果 x 不是对象或函数,则用 x 实现 promise。
如果一个 promise 用一个参与循环 thenable 链的 thenable 解析, 这样[[Resolve]](promise, thenable)的递归性质最终会导致[[Resolve]](promise, thenable) 被再次调用,如下上述算法会导致无限递归。 鼓励但不要求实现以检测此类递归并拒绝以信息丰富的 TypeError 作为原因的承诺。
3. 笔记
-
这里的
平台代码是指引擎、环境和 promise 实现代码。在实践中,这个要求确保 onFulfilled 和 onRejected 异步执行,在调用 then 的事件循环之后,并使用新的堆栈。 这可以通过“宏任务”机制(例如setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。 由于 promise 实现被认为是平台代码,它本身可能包含一个任务调度队列或“trampoline”,在其中调用处理程序。 -
也就是说,在严格模式下,这在它们内部是未定义的;在草率模式下,它将是全局对象。
-
实现可以允许 promise2 === promise1,前提是实现满足所有要求。每个实现都应该记录它是否可以产生 promise2 === promise1 以及在什么条件下。
-
通常,只有当 x 来自当前实现时,才会知道 x 是一个真正的承诺。该条款允许使用特定于实现的手段来采用已知符合承诺的状态。
-
这个首先存储对 x.then 的引用,然后测试该引用,然后调用该引用的过程避免了对 x.then 属性的多次访问。这些预防措施对于确保访问器属性的一致性很重要,访问器属性的值可能会在检索之间发生变化。
-
实现不应对 thenable 链的深度设置任意限制,并假设超出该任意限制递归将是无限的。只有真正的循环才会导致 TypeError;如果遇到无限的不同 thenable 链,则永远递归是正确的行为。
4. 易错点
- Promise.then 返回的是一个 新的 Promise 对象。
- Promise.all | Promise.race | Promise.allSettled | Promise.any 当且仅当传入的可迭代对象为空时为同步。
- Promise.all | Promise.race | Promise.allSettled | Promise.any 参数的均是 可迭代对象(不仅仅只是数组)。
- Promise.race 传入的是空的可迭代对象的话, 会一直等待。
5. 源码实现
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const isObject = (obj) => obj !== null && typeof obj === "object";
const isFunction = (param) => typeof param === "function";
class Promise {
constructor(executor) {
if (typeof executor !== "function") {
throw new TypeError("executor is not a function");
}
this.status = PENDING;
this.value = null;
this.reason = null;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (value instanceof Promise) {
// 2.3.2
// 递归解析resolve中的参数 直到不是promise对象
value.then(resolve, reject);
return;
}
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
// 如无以下方法,则无法处理异步操作
this.onResolvedCallbacks.forEach((callback) => callback(value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 如无以下方法,则无法处理异步操作
this.onRejectedCallbacks.forEach((callback) => callback(reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
static resolve(value) {
return new Promise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
then(onFulfilled, onRejected) {
// 2.2.1.1
onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;
// 2.2.1.2
onRejected = isFunction(onRejected)
? onRejected
: (reason) => {
throw reason;
};
let promise2;
return (promise2 = new Promise((resolve, reject) => {
switch (this.status) {
case FULFILLED:
// 没有用 queueMicrotask 则 promise2 不存在 (new对象的执行过程)
queueMicrotask(() => {
try {
const x = onFulfilled(this.value); // 2.2.7.1
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 用了 queueMicrotask 则不会走类里面的 try catch
reject(error); // 2.2.7.2
}
}); // 2.2.4 | 2.2.2.2
break;
case REJECTED:
queueMicrotask(() => {
try {
const x = onRejected(this.reason); // 2.2.7.1
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 用了 queueMicrotask 则不会走类里面的 try catch
reject(error); // 2.2.7.2
}
}); // 2.2.4 | 2.2.2.2
break;
case PENDING:
this.onResolvedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.value); // 2.2.7.1
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 用了 queueMicrotask 则不会走类里面的 try catch
reject(error); // 2.2.7.2
}
}); // 2.2.4 | 2.2.2.2
});
}
})); // 2.2.7
}
catch(onRejected) {
return this.then(null, onRejected);
}
/**
* finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。
* 这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
* 这避免了同样的语句需要在then()和catch()中各写一次的情况。
* @param onFinally
* @returns {Promise}
*/
finally(onFinally) {
return this.then(
(value) => Promise.resolve(onFinally()).then(() => value),
(reason) =>
Promise.resolve(onFinally()).then(() => {
throw reason;
})
);
}
/**
* @param values 一个可迭代对象,如 Array 或 String。
* @returns 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved) Promise。
注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise。
其它情况下返回一个处理中(pending)的Promise。
这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。
返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。
*/
static all(values) {
return new Promise((resolve, reject) => {
values = [...values];
if (values.length === 0) {
return resolve(values);
}
let counter = 0;
const result = [];
values.forEach((val, index) => {
Promise.resolve(val)
.then((res) => {
result[index] = res;
// 易错点
// 需要一个计数器来确保实际完成的数量
// for example: 可能 values 的最后一项是最先完成的, 所以 如果用 result.length 来判断的话,会导致误以为全部完成的情况发生
counter++;
if (counter === values.length) {
resolve(result);
}
})
.catch(reject);
});
});
}
/**
* 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
* @param values
* @returns {Promise}
*/
static allSettled(values) {
return new Promise((resolve, reject) => {
values = [...values];
const result = [];
let counter = 0;
if (values.length === 0) {
return resolve(values);
}
values.forEach((value, index) => {
Promise.resolve(value)
.then((val) => {
result[index] = {
status: FULFILLED,
value: val,
};
counter++;
if (counter === values.length) {
resolve(result);
}
})
.catch((reason) => {
result[index] = {
status: REJECTED,
reason,
};
counter++;
if (counter === values.length) {
resolve(result);
}
});
});
});
}
/**
* 一旦迭代器中的某个promise解决或拒绝,返回的 promise 就会解决或拒绝。
* @param values 只要是一个可迭代的对象即可
* 目前所有的内置可迭代对象如下:String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。
* @returns {Promise}
*/
static race(values) {
return new Promise((resolve) => {
values = [...values];
values.forEach((value) => {
resolve(value);
});
});
}
/**
* 只要迭代器中的一个 promise 成功,就返回那个已经成功的 promise,
* 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例
* @param values 只要是一个可迭代的对象即可
* 目前所有的内置可迭代对象如下:String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。
* @returns {Promise}
*/
static any(values) {
return new Promise((resolve, reject) => {
values = [...values];
const reasons = [];
let count = 0;
values.forEach((value, index) => {
Promise.resolve(value)
.then(resolve)
.catch((error) => {
reasons[index] = error;
count++;
if (count === values.length) {
reject(new AggregateError(reasons));
}
});
});
});
}
}
const resolvePromise = (promise2, x, resolve, reject) => {
let isCalled = false;
if (promise2 === x) {
reject(new TypeError("Chaining cycle detected for promise")); // 2.3.1
return;
}
// 为了能兼容其他符合 PromiseA+标准的promise 如: bluebird q es6-promise
if (x instanceof Promise) {
// 2.3.2 可以不写,与 2.3.3 一个逻辑
// 这边需要加深印象
queueMicrotask(() => {
x.then(
(y) => {
resolvePromise(promise2, y, resolve, reject);
},
(e) => {
reject(e);
}
); // 2.3.2.1
});
} else if (isObject(x) || isFunction(x)) {
// 2.3.3
try {
let then = x.then; // 2.3.3.1
if (isFunction(then)) {
queueMicrotask(() => {
then.call(
x,
(y) => {
if (isCalled) return;
isCalled = true;
resolvePromise(promise2, y, resolve, reject); // 2.3.3.3.1
},
(e) => {
if (isCalled) return;
isCalled = true;
reject(e); // 2.3.3.3.3
}
); // 2.3.3.3
});
} else {
resolve(x); // 2.3.3.4
}
} catch (error) {
if (isCalled) return; // 2.3.3.3.4.1
isCalled = true;
reject(error); // 2.3.3.2 | 2.3.3.3.4.1
}
} else {
resolve(x); // 2.3.4
}
};
6. 习题
Promise.resolve()
.then(() => {
console.log(0);
setTimeout(() => {
console.log("宏任务");
}, 0);
return Promise.resolve(4);
})
.then((res) => {
console.log(res);
});
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
})
.then(() => {
console.log(7);
});