在了解promise之前,需要有一些前置基本知识点
任务队列(消息队列)
- 就是一个队列的数据结构,里面存放要执行的任务
- 任务队列的任务类型有鼠标滚动、点击、移动、计时器、websocket、文件的读写、DOM解析、样式计算、布 局计算、js的执行等等
- 上述任务都在主线程中执行,由于js单线程的机制,一个任务得等它前面的任务都执行完毕它才能执行,可能会遇到单个任务执行时间过长,一直占用主线程的情况。
- 具体的业务场景:对于频繁改变DOM元素的js任务来说,每次变化都需要调用不同的js接口,导致任务的时间拉长,而如果把这个任务做成异步执行,在添加到任务队列之前,它前面也可能存在很多任务的排队 所以,为了处理高优先级的任务,解决单线程任务执行时间过长的问题,将任务切分为宏任务和微任务.
异步任务
- 在进程执行某个任务时,任务要等待一段时间才能返回,所以就把这个任务放到专门处理异步任务的模块,继续执行任务队列中的其他模块,防止消息队列发生阻塞。
- 常见的异步任务:定时器、ajax、事件绑定、async await、promise
微任务和宏任务
- 任务队列中的每一个任务都是宏任务,在执行的过程中,如果有微任务产生,就添加到微任务队列中去
异步请求(如超时报错等功能 具有很多的应用场景)
| 宏任务 | 微任务 |
|---|---|
| 渲染事件 | promise[then/catch/finally] |
| 请求 | proxy |
| script代码块 | MutationObserver |
| setTimeout | process.nextTick |
| setInterval | queueMicrotask(用来创建微任务) |
| setimemediate/ I/O | async/await |
注意:当前宏任务里的微任务全部执行完,才会执行下一个宏任务,构成一个事件循环(执行都是把任务压入执行栈来执行对应的函数操作的)
Promise 术语
- promise 是一个有then方法的对象或者是函数,行为遵循本规范
- thenable 是一个有then方法的对象或者是函数
- value 是promise状态成功时的值,也就是resolve的参数, 包括各种数据类型, 也包括undefined/thenable或者是 promise
- reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因
- exception 是一个使用throw抛出的异常值
Promise 规范
Promise States
promise应该有三种状态. 要注意他们之间的流转关系.
1. pending
1.1 初始的状态, 可改变.
1.2 一个promise在resolve或者reject前都处于这个状态。
1.3 可以通过 resolve -> fulfilled 状态;
1.4 可以通过 reject -> rejected 状态;
2. fulfilled
2.1 最终态, 不可变.
2.2 一个promise被resolve后会变成这个状态.
2.3 必须拥有一个value值
3. rejected
3.1 最终态, 不可变.
3.2 一个promise被reject后会变成这个状态
3.3 必须拥有一个reason
状态流转
Tips: 总结一下, 就是promise的状态流转是这样的
pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected
then
promise应该有个then方法,用来访问最终的结果,无论是value还是reason。
promise.then(onFulfilled, onRejected)
1、参数要求
- onFulfilled必须是函数类型,可选,如果不是函数,应该被忽略
- onRejected必须是函数类型,可选,如果不是函数,应该被忽略
2、onFulfilled特性
- 在promise变为fulfilled时,应该调用onFulfilled,参数是value
- 在promise变成fulfilled之前,不应该被调用
- 只能被调用一次(实现时需使用变量来限制执行次数)
3、onRejected特性
- 在promise变成rejected时,应该调用onRejected,参数是reason
- 在promise变成rejected之前,不应该被调用
- 只能被调用一次
4、onFulfilled和onRejected应该是微任务
在执行上下文堆栈仅包含平台代码之前,不得调onFulfilled 或 onRejected函数,onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严格模式下指向 window),使用queueMicrotask或者setTimeout来实现微任务的调用
5、then方法可以被调用多次
- promise状态变成fulfilled后,所有的onFulfilled回调都需要按照then的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个onFulfilled的回调)
- promise状态变成rejected后,所有的onRejected回调都需要按照then的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个onRejected的回调)
6、then必须返回一个promise
then必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected)
- onFulfilled或onRejected执行的结果是x,调用resolvePromise
- 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为e
- 如果onFulfilled不是一个函数且promise1已经fulfilled,promise2以promise1的value触发onFulfilled
- 如果onRejected不是一个函数且promise1已经rejected,promise2以promise1的reason触发onRejected
7、Promise的解决过程resolvePromise
resolvePromise(promise2, x, resolve, reject)
- 如果 x是当前 promise 本身(promise2和x相等),那么reject TypeError
- 如果 x是另一个 promise(即x是一个promise),那么沿用它的 state 和 result 状态
- 如果x是pending态,那么promise必须要在pending,直到x变成fulfilled或者rejected
- 如果x是fulfilled态,用相同的value执行promise
- 如果x是rejected态,用相同的reason拒绝promise
- 如果x是一个object或者是一个function(不常见)
- 首先取x.then的值,let then = x.then
- 如果取x.then这步出错抛出e,那么以e为reason拒绝promise
- 如果then是一个函数,将x作为函数的作用域this调用,即then.call(x, resolvePromise, rejectPromise),第一个参数叫resolvePromise,第二个参数叫rejectPromise
- 如果resolvePromise以y为参数被调用,则执行resolvePromise(promise2, y, resolve, reject)
- 如果rejectPromise 以 r为参数被调用,则以r为reason拒绝 promise
- 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
- 如果调用then抛出异常e:若 resolvePromise 或 rejectPromise 已经被调用,那么忽略,否则以e为reason拒绝promise
- 如果then不是一个function,以x为value执行promise
- 如果x不是object或者function,以x为value执行promise
手动实现 Promise
// 2.定义三种状态类型
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 1.初始化class
class MPromise {
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING; //私有变量
constructor(fn) {
// 3.设置初始状态
this.status = PENDING;
this.value = null;
this.reason = null;
/**
* 5.promise构造函数入参
* 5.1.入参是一个函数,函数接收两个参数,resolve,reject
* 5.2.new promise的时候,就要执行这个函数,并且有任何错误都要被reject出去
*/
try{
// 初始化后立马会执行
fn(this.resolve.bind(this), this.reject.bind(this));
}catch(e){
this.reject(e)
}
}
// 7. 监听状态 实现get set 额外的值存储,避免死循环
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;
}
}
// 4.更改status,pending -> fulfilled/rejected; 入参value,reason
resolve(value) {
if(this.status === PENDING){
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;
}
}
// 6.实现then函数
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) => {
// 8
const fulfilledMicroTack = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
}
const rejectedMicroTack = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
switch(this.status) {
case FULFILLED:
fulfilledMicroTack();
break;
case REJECTED:
rejectedMicroTack()
break;
case PENDING:
this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled);
this.REJECTED_CALLBACK_LIST.push(realOnRejected);
break;
}
});
return promise2;
}
// 10. 实现catch
catch(onRejected) {
return this.then(null, onRejected);
}
// 9.
resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new TypeError("The promise and the return value are the same"))
}
if(x instanceof MPromise) {
queueMicrotask(() => {
x.then((y) => {
this.resolvePromise(promise2, y, resolve, reject);
})
})
}else if(typeof x==='object' || this.isFunction(x)) {
if(x === null) {
return resolve(x);
}
let then = null;
try {
then = x.then;
} catch (e) {
return reject(e);
}
if(this.isFunction(then)) {
//限制调用次数
let called = false;
try {
then.call(x,(y) => {
if(called) {
return;
}
called = true;
this.resolvePromise(promise2,y,resolve,reject);
},(r) => {
if(called){
return;
}
called = true;
reject(r);
})
} catch (error) {
if(called) {
return;
}
reject(error);
}
}else {
resolve(x);
}
}else{
resolve(x)
}
}
// 判断接收的参数是否是函数
isFunction (param) {
return typeof param === 'function'
}
//静态方法
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);
})
}
}
调用 Promise 总结问题
1. 为什么promise resolve了一个value, 最后输出的value值确是undefined
const test = new MPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then((value) => {
console.log('then');
});
setTimeout(() => {
console.log(test);
}, 3000)
- 因为现在这种写法, 相当于在.then里return undefined, 所以最后的value是undefined。
- 如果显式return一个值, 就不是undefined了;比如return value.
2. 为什么我在catch的回调里, 打印promise, 显示状态是pending
const test = new MPromise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
}).catch((reason) => {
console.log(`[catch] reason=${reason}`);
console.log(test) //status: pending
});
setTimeout(() => {
console.log(test); //status: fulfilled
}, 3000)
- catch 函数会返回一个新的promise, 而test就是这个新promise
- catch 的回调里, 打印promise的时候, 整个回调还并没有执行完成(所以此时的状态是pending), 只有当整个回调完成了, 才会更改状态
- catch 的回调函数, 如果成功执行完成了, 会改变这个新Promise的状态为fulfilled
实现promise.all 方法
Promise.all = (arr) => {
return new Promise((resolve, reject) => {
let res = [];
let count = 0;
for (let i = 0; i < arr.length; i++) {
// 面试点:
Promise.resolve(arr[i]).then((value) => {
//坑点1:切记不能使用res.push(value); 因为此时属于异步,如果使用push,则会将原数组循序混乱,所以使用赋值方式避免
res[i] = value;
//坑点2:切记不能直接使用res.length === arr.length;因为异步如果先执行了arr[1],arr.length长度会直接是2,所以需要记录,在判断记录值是否等于arr.length
count ++;
if(count === arr.length) {
resolve(res);
}
}).catch((reason) => {
reject(reason);
})
}
})
}
实现promise.prototype.finally 方法
Promise.prototype.finally = function(callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => value);
}),
(err) => {
return Promise.resolve(callback()).then(() => {throw err});
}
}
实现Promise.allSeettled 方法, 需要返回所有promise的状态和结果
function PromiseAllSettled(promiseArray) {
return new Promise(function (resolve, reject) {
//判断参数类型
if (!Array.isArray(promiseArray)) {
return reject(new TypeError('arguments muse be an array'))
}
let counter = 0;
const promiseNum = promiseArray.length;
const resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promiseArray[i])
.then((value) => {
resolvedArray[i] = {
status: 'fulfilled',
value
};
})
.catch(reason => {
resolvedArray[i] = {
status: 'rejected',
reason
};
})
.finally(() => {
counter++;
if (counter == promiseNum) {
resolve(resolvedArray)
}
})
}
})
}