Promise
目标
- Promise A+规范
- 手写Promise
- 题目练习
Promise A+规范
术语
- promise: 一个拥有符合这个规范的行为的then方法的对象或函数。
- thenable: 定义了一个then方法的对象或函数。
- value: 任意合法的JavaScript值(包括undefined,thenable,promise)。
- exception: 使用throw语句抛出的一个值
- reason: 表示promise为什么被拒绝的一个值
必要条件
Promise 状态
promise必须是这三个状态中的一种:等待态pending,解决态fulfilled或拒绝态rejected
- 当promise处于
pending
状态的时候:
可能变为fulfilled
或者rejected
状态。 - 当promise处于
fulfilled
状态的时候:
2.1 一定不能转换为任何其它状态
2.2 必须有一个不能改变的value
- 当promise处于
rejected
状态的时候:
3.1 一定不能转换为任何其它状态
3.2 必须有一个不能改变的reason
在这里,"一定不能改变"意味着不变的身份(例如 ===),但是并不意味着深度不可变性。(译注者:这里应该是说只要值的引用相同即可,并不需要引用中的每一个值都相等)
then方法
Promise
必须提供一个then
方法来访问当前或最终的value
或reason
。
Promise的then方法接受俩个参数:
promise.then(onFulfilled, onRejected)
onFulfilled
和onRejected
都是可选的参数
1.1. 如果onFulfilled
不是一个函数,它必须被忽略
1.2. 如果onRejected
不是一个函数,它必须被忽略- 如果
onFulfilled
是一个函数
2.1. 在promise变成fulfilled
时,应该调用 onFulfilled, 参数是value
2.2. 在promise变成fulfilled
之前, 不应该被调用.
2.3. 它一定不能被调用多次。 - 如果
onRejected
是一个函数
2.1. 在promise变成rejected
时,应该调用 onRejected, 参数是reason
2.2. 在promise变成rejected
之前, 不应该被调用.
2.3. 它一定不能被调用多次。 - 在执行上下文栈中只包含平台代码之前,onFulfilled或onRejected一定不能被调用
- 同一个promise上的then可能被调用多次
6.1. 如果promise被解决,所有相应的onFulfilled回调必须按照他们原始调用then的顺序执行
6.2. 如果promise被拒绝,所有相应的onRejected回调必须按照他们原始调用then的顺序执行 - then必须返回一个promise对象
promise2 = promise1.then(onFulfilled,onRejected)
resolvePromise
resolvePromise(promise2, x, resolve, reject)
- 如果promise和x引用同一个对象,用一个TypeError作为原因来拒绝promise
- 如果x是一个promise,根据
x
的状态:
2.1. 如果x是pending
,promise必须保持等待状态,直到x被解决或拒绝
2.2. 如果x是fulfilled
,用相同的value
解决promise
2.3. 如果x是rejected
,用相同的reason
拒绝promise 否则,如果x是一个对象或函数
3.1.让then成为x.then
let then = x.then.
3.2. 如果检索属性x.then导致抛出了一个异常e,用e作为原因拒绝promise
3.3. 如果then是一个函数,用x作为this调用它。then方法的参数为俩个回调函数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:3.3.1. 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)。译者注:这里再次调用[[Resolve]](promise,y),因为y可能还是promise 3.3.2. 如果rejectPromise用一个原因r调用,用r拒绝promise。译者注:这里如果r为promise的话,依旧会直接reject,拒绝的原因就是promise。并不会等到promise被解决或拒绝 3.3.3. 如果`resolvePromise`和`rejectPromise`都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。译者注:这里主要针对thenable,promise的状态一旦更改就不会再改变。 3.3.4. 如果调用then抛出了一个异常e, 3.3.4.1 如果`resolvePromise`或`rejectPromise`已经被调用,忽略它 3.3.4.2 否则,用`e`作为原因拒绝promise
3.4. 如果then不是一个函数,用x解决promise
手写Promise
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
/**
* 1. 构建Promsie类
* 2. 定义3种状态类型
* 3. 设置初始状态
* 4. resolve和reject方法
* 5. 定义构造参数
* 5.1 入参是一个函数, 函数接收resolve和reject两个参数.
* 5.2 注意在初始化promise的时候, 就要执行这个函数, 并且有任何报错都要通过reject抛出去
* 6. 实现then方法
*/
class MPromise {
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING;
constructor(fn) {
// this.status = PENDING;
this.value = null;
this.reason = null;
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(e.message);
}
}
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
switch (newStatus) {
case FULFILLED: {
this.FULFILLED_CALLBACK_LIST.forEach((callback) => {
callback(this.value);
});
break;
}
case REJECTED: {
this.REJECTED_CALLBACK_LIST.forEach((callback) => {
callback(this.reason);
});
break;
}
}
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: (value) => value;
const realOnRejected = this.isFunction(onRejected)
? onRejected
: (reason) => {
throw reason;
};
const promise2 = new MPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask();
break;
}
case REJECTED: {
rejectedMicrotask();
break;
}
case PENDING: {
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
}
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
if (promise2 === x) {
return reject(
new TypeError("The promise and the return value are the same")
);
}
if (x instanceof MPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
queueMicrotask(() => {
x.then((y) => {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
});
} else if (typeof x === "object" || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果then是函数
if (this.isFunction(then)) {
let called = false;
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
(y) => {
// 需要有一个变量called来保证只调用一次.
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
static resolve(value) {
if (value instanceof MPromise) {
return value;
}
return new MPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MPromise((resolve, reject) => {
reject(reason);
});
}
static race(promiseList) {
return new MPromise((resolve, reject) => {
const length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (let i = 0; i < length; i++) {
MPromise.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
}
);
}
}
});
}
catch(callback) {
return this.then(null, callback);
}
static all(promiseList) {
let results = [];
let promiseCount = 0;
let promisesLength = promiseList.length;
return new MPromise((resolve) => {
Promise.resolve(val).then(
(res) => {
promiseCount++;
results[i] = res;
if (promiseCount === promisesLength) {
return resolve(results);
}
},
(err) => {
return reject(err);
}
);
});
}
isFunction(param) {
return typeof param === "function";
}
}
校验Promsie是符合规范
安装promises-aplus-tests
npm i promises-aplus-tests -D
暴露接口
MPromise.defer = MPromise.deferred = function(){ let dfd = {}; dfd.promise = new MPromise((resolve, reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = MPromise
运行,全部passing
npx promises-aplus-tests .\promise.js
练习
- 求运行结果
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2) // 这里有疑惑
})
promise.then(() => {
console.log(3)
})
console.log(4)
运行结果:
1
2
4
3
- 求运行结果
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!') // 这里不太确定 status
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
运行结果
promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
<rejected> Error: error!!!
at promise.then (...)
at <anonymous> }
- 求运行结果
const promise = new Promise((resolve, reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
运行结果
then: success1
- 求运行结果
Promise.resolve(1)
.then((res) => {
console.log(res)
return 2
})
.catch((err) => {
return 3
})
.then((res) => {
console.log(res)
})
运行结果
1
2
catch如果成功执行,会返回相同的value也就是2.
- 求运行结果
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
运行结果
once
success 1009
success 1009
- 求运行结果
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
运行结果
then: Error: error!!!
at Promise.resolve.then (...)
at ...
then的onFulfilled函数里面修改rejected的:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
- 求运行结果
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)
运行结果
TypeError: Chaining cycle detected for promise #<Promise>
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:667:11)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:607:3
- 求运行结果
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
运行结果
1
解释:then函数的onFulfilled不为函数,默认为(value)=>value
- 求运行结果
Promise.resolve()
.then(function success (res) {
throw new Error('error')
}, function fail1 (e) {
console.error('fail1: ', e)
})
.catch(function fail2 (e) {
console.error('fail2: ', e)
})
运行结果
fail2: Error: error
at success (...)
at ...
解释: resolve
函数执行完返回fullfile
状态的promsie对象。在第一个then
函数的onFullfield
抛出异常。则返回promsie对象的状态为rejected
。被catch
函数捕获。
- 求运行结果
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
end
nextTick
then
setImmediate
解释: process.nextTick
和 promise.then
都属于 microtask,而 setImmediate
属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。
小结
promise的实现最难理解的就是resolvePromise
,非常的绕。另外学习到的了一个新的APIqueueMicrotask
,有些文章是使用setTimeout
来模拟实现微任务调用。
setTimeout(() => {},0)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。