前言
开始之前我们先了解一下以下几个概念
一、什么是宏任务与微任务?
Js 是单线程,但是一些高耗时操作就带来了进程阻塞问题。
为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。
在异步模式下,创建异步任务主要分为『宏任务』与『微任务』两种。
宏任务:
就是JS 内部(任务队列里)的任务,严格按照时间顺序压栈和执行,可以理解是每次执行栈执行的代码就是一个宏任务。
如:
script(整体代码)、
setTimeout、
setInterval、
I/O、
UI交互事件、
postMessage、
MessageChannel、
setImmediate(Node.js 环境)
微任务:
通常来说就是需要在当前任务执行结束后立即执行的任务,
例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的任务,
这样便可以减小一点性能的开销。
如:
promise、
Object.observe、
MutationObserver、
process.nextTick(Node.js 环境)
二、EventLoop
事件循环机制,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用「异步」的原理
由三部分组成:
- 调用栈
- 微任务队列
- 消息队列(宏任务队列)
运行机制
在事件循环中,每进行一次循环操作称为tick,每一次tick的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取) 如图:
三、发布-订阅者模式
发布订阅者模式其实是一种对象间一对多的依赖关系(利用消息队列)
定义: 当一个对象的状态(state)发生改变时,所有依赖于它的对象都得到状态改变通知订阅者(Subscriber)把自己想订阅的事件注册到调度中心(Event Channel),当发布者(Pubblisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码
实现思路:
· 创建一个类
· 该类上创建一个缓存列表
· on方法用来吧函数Fn都加到缓存列表(调度中心)
· emit方法取到event事件类型,根据event值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
· off方法可以根据event事件类型取消订阅
class Observer {
constructor() {
// 消息队列
this.message = {}
}
// 向消息队列中添加事件类型、事件函数
on(type, fn){
// 没有当前事件类型,初始化一个当前类型
if(!this.message[type]){
this.message[type] = []
}
// 当前事件类型存在,将fn添加到该类型下
this.message[type].push(fn)
}
// 向消息队列内的事件发送执行
emit(type){
if(!this.message[type]) return
this.message[type].forEach(element => {
element()
});
}
// 删除消息队列中的事件类型或某个事件函数
off(type, fn){
// 1、判断是否有当前事件类型
if(!this.message[type]) return
// 2、判断是否有fn参数,没有fn则直接删除当前事件类型下的所有事件函数
if(!fn){
delete this.message[type]
return
}
// 3、删除消息队列中该类型下的与当前fn相同的事件函数
this.message[type] = this.message[type].filter(item => item != fn)
}
}
// 向person委托一些内容
const person = new Observer()
person.on('abc', fn1)
person.on('def', fn2)
person.on('abc', fn3)
person.off('abc', fn1)
person.emit('abc')
console.log(person)
function fn1(){
console.log('fn1')
}
function fn2(){
console.log('fn2')
}
function fn3(){
console.log('fn3')
}
// 执行结果: Observer { message: { def: [ [Function: fn2] ] } }
实现手写promise
咱们先看一下原生promise的执行和基本属性,上代码:
let promise = new Promise((resolve, reject) => {
resolve('fulfilled'); // 成功状态 fulfilled
reject('rejected'); // 失败状态 rejected
throw new Error('error') // 异常抛出,返回结果为:失败状态 rejected
})
promise.then((value) => { // then方法中提供两个参数 1. 成功回调 2.失败的回调
console.log(value, 'success') // 会把成功的结果返回
}, (reason) => {
console.log(reason, 'fail') // 会把失败的结果返回
})
执行结果为:fulfilled success
这里暴露出了四个知识点:
- 1、执行了
resolve,Promise状态会变成fulfilled - 2、执行了
reject,Promise状态会变成rejected - 3、Promise只以
第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected - 4、Promise中除了调用
resolve和reject能改变状态外,还可以用throw抛出异常,也会执行到reject的失败逻辑
一、实现resolve、reject、then方法
咱们先来确定promise的几个必要因素:
1、Promise会有三个状态:
pending: 等待状态
fulfilled: 成功状态
rejected: 失败状态
2、Promise中的函数会立即执行、那就会用到 executor函数
3、会有resolve,reject两个方法,并且会在这两个方法中
4、then()方法中会根据当前状态调用resolve,reject的执行的结果,并且进行返回
5、throw抛出异常,也会执行到reject的失败逻辑
// 初始化三种状态
const PENDING = 'pending'; // 默认等待态
const FULFILLED = 'fulfilled'; // 成功态
const REJECTED = 'rejected'; // 失败态
// 创建Promise类
class Promise {
constructor(executor) { // executor 会默认被执行 同步执行
this.status = PENDING; // 状态
this.value = undefined; // 成功的结果
this.reason = undefined; // 失败的结果
const resolve = (value) => {
this.value = value; // 这里保存le成功状态的结果
this.status = FULFILLED // 同时对状态进行修改
}
const reject = (reason) => {
this.reason = reason; // 这里保存了失败状态的结果
this.status = REJECTED;
}
// 如果执行时出错,我们将错误传递到reject中,执行到了失败的逻辑
try {
executor(resolve, reject); // 默认new Promise中的函数会立即执行
} catch (e) {
reject(e)
}
}
// onFulfilled: 成功的回调 onRejected:失败的回调结果
then(onFulfilled, onRejected) {
if (this.status == FULFILLED) { // 状态为成功时,将返回成功结果的值
onFulfilled(this.value)
}
if (this.status == REJECTED) { // 状态为失败时,将返回失败结果的值
onRejected(this.reason);
}
}
}
测试一下代码:
let promise1 = new Promise((resolve, reject) => {
resolve('fulfilled');
})
promise1.then((value) => {
console.log(value, 'success')
}, (reason) => {
console.log(reason, 'fail')
})
// 执行结果为: fulfilled,fail
let promise2 = new Promise((resolve, reject) => {
reject('rejected');
})
promise2.then((value) => {
console.log(value, 'success')
}, (reason) => {
console.log(reason, 'fail')
})
// 执行结果为: rejected,fail
let promise3 = new Promise((resolve, reject) => {
resolve('fulfilled');
reject('rejected');
})
promise3.then((value) => {
console.log(value, 'success')
}, (reason) => {
console.log(reason, 'fail')
})
// 执行结果为: rejected,fail
二、状态不可变
这里就有问题了,上边代码中,promise3正确的返回值应该为:fulfilled,success而不是当前的【rejected,fail】,因为这里调用了两次之后,第一次修改的状态为成功,第二次又进行了修改,修改为了失败。Promise的状态应该为:状态一旦确定不在进行改变,所有这里就要进行优化,当状态为pending的时候才能对状态进行修改,其他状态不可修改!
const resolve = (value) => {
if(this.status === PENDING){ // 当状态为pending的时候才能修改
this.value = value
this.status = FULFILLED
}
}
const reject = (reason) => {
if(this.status === PENDING){ // 当状态为pending的时候才能修改
this.reason = reason
this.status = REJECTED
}
}
到这里一个丐版的Promise已经实现了,但是这还有很多问题,来我们接着往下走
三、定时器问题
我们先来看一段代码
let promise = new Promise((resolve, reject) => {
// 我在这加了一个setTimeout
setTimeout(() => {
resolve('fulfilled') // 让promise变成成功态
reject('rejected') // 让promise变成失败态
}, 1000);
})
promise.then((value) => {
console.log(value, 'success') // 成功后返回值
}, (reason) => {
console.log(reason, 'fail') // 失败后返回值
})
// 执行结果为: 空 没反应
在Promise中加了一个定时器后,输出结果为空(无反应),这是什么原因呢......,在.then()
的时候呢,Promise的状态还没改变,还是Pending状态,一秒以后状态才发生了变化,然后才有了返回结果,所以当我直接执行.then()方法的时候输出为空的结果。
这块呢就涉及一个设计模式了:发布订阅模式。需要将then中的两个回调函数:onFulfilled和onRejected先进行存储,当执行resolve方法或者reject方法,执行完成以后再执行onFulfilled和onRejected回调
const PENDING = 'pending'; // 默认等待态
const FULFILLED = 'fulfilled'; // 成功态
const REJECTED = 'rejected'; // 失败态
class Promise {
constructor(executor) { // executor 会默认被执行 同步执行
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 用户调用resolve和reject 可以将对应的结果暴露在当前的promise实实例上
this.onResolvedCallbacks = []; // 存储then成功回调的函数集合
this.onRejectedCallbacks = []; // 存储then失败回调的函数集合
const resolve = (value) => {
if (this.status == PENDING) {
this.value = value;
this.status = FULFILLED
// 调用了成功的逻辑
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
const reject = (reason) => {
if (this.status == PENDING) {
this.reason = reason;
this.status = REJECTED;
// 调用了失败的逻辑
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
try {
executor(resolve, reject); // 默认new Promise中的函数会立即执行
} catch (e) { // 如果执行时出错,我们将错误传递到reject中 -》 执行到了失败的逻辑
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.status == FULFILLED) {
onFulfilled(this.value)
}
if (this.status == REJECTED) {
onRejected(this.reason);
}
if (this.status == PENDING) { // 处理异步状态下的回调
this.onResolvedCallbacks.push(() => {
// todo 可以实现其他逻辑
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
// todo 可以实现其他逻辑
onRejected(this.reason);
})
}
}
}
测试一下:
let promise = new Promise((resolve, reject) => {
// 我在这加了一个setTimeout
setTimeout(() => {
resolve('fulfilled') // 让promise变成成功态
reject('rejected') // 让promise变成失败态
}, 1000);
})
promise.then((value) => {
console.log(value, 'success') // 成功后返回值
}, (reason) => {
console.log(reason, 'fail') // 失败后返回值
})
// 执行结果为: fulfilled success
就很完美啊
四、then链式调用
- then方法是支持链式调用的,链式调用的好处就是可以解决
回调地狱的问题 - then方法中,成功的回调或者失败的回调返回的是一个promise,那么会采用返回的promise的状态,走外层下一次then中的成功或失败, 同时将promise处理后的结果
向下传递 - then方法中 成功的回调或者失败的回调返回的是一个
普通值 (不是promise)这里会将返回的结果传递到下一次then的成功中去,并将值传递下去 - 如果在then方法中 成功的回调或者失败的回调 执行时出错会走到外层下一个then中的失败中去
举个小🌰
const promise = new Promise((resolve, reject)=>{
resolve('ok')
})
promise.then((res)=>{
console.log(res, 'fulfilled') // 打印结果: ok fulfilled
// 返回了一个新的Promise,该Promise的状态会直接影响下一个then的回调结果
return new Promise((resolve, reject)=>{
reject('fail')
})
}, (err)=>{
console.log(err, 'rejected')
}).then((res)=>{
console.log(res, 'fulfilled-1') // 打印结果: fail rejected-1
}, (err)=>{
console.log(err, 'rejected-1')
})
如果返回的是一个失败的Promise或者报错了,才会走下一个then的失败,否则全部走成功
那如何实现一个链式调用呢?
return new Promise() 每次都产生一个全新的Promise来保证状态可以正常的切换,不会相互影响,让我们来看一下代码是怎么实现的:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function'? onFulfilled:v=>v;
onRejected = typeof onRejected == 'function' ? onRejected : e=>{throw e};
// 每次调用then方法 都必须返回一个全新的promise
let promise2 = new Promise((resolve, reject) => {
if (this.status == FULFILLED) {
// 这块为什么包一个定时器?
// 解决微任务,立即执行的问题
setTimeout(() => {
try {
// x 就是上一个then成功或者失败的返回值,这个x决定proomise2 走成功还是走失败
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
});
return promise2;
}
这里呢有一个X值的判断逻辑,需要判断当前返回值X的类型
1.如果x是一个普通值,则直接调用resolve即可
2.如果x是一个promise那么应该采用这个promise的状态 决定调用的是 resolve还是reject
function resolvePromise(x, promise2, resolve, reject) { // 我们还需要考虑 x 可能是别人家的promise
// 希望我的promise可以和别人的promise一起来混用的 q库 bluebird库
// If promise and x refer to the same object, reject promise with a TypeError as the reason
if (x === promise2) {
return reject(new TypeError('循环引用'))
}
// 继续判断x 是不是一个promise promsise需要有then方法 (啥时候是函数的? 别人写的proimise 就有可能是函数)
if ((typeof x === 'object' && x !== null) || (typeof x == 'function')) {
// 才有可能是一个promise, 继续判断x 是否有then
// Let then be x.then
let called = false;
try {
let then = x.then; // 尝试取then方法
if (typeof then == 'function') { // 我就认为他是promise了
// x.then // 这个会再次取一次属性 触发get方法
// then.call(x) // 这个不会
then.call(x, (y) => { // y有可能还是一个promise ,所以要再次进行解析流程
// 我需要不停的解析成功的promise中返回的成功值,直到这个值是一个普通值
if(called) return;
called = true
resolvePromise(y,promise2,resolve,reject);
}, (r) => {
if(called) return;
called = true
reject(r);
});
} else { // {then:1}
resolve(x);
}
} catch (e) {
if(called) return;
called = true
reject(e); // 让promise2 变成失败态
}
} else {
// x 是一个普通值
resolve(x);
}
}
五、Promise.all()
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果Promise全都成功,则返回成功结果数组
- 如果有一个Promise失败,则返回这个失败结果
咱们先来看一下原生的用法:
let promise1 = new Promise((resolve, reject) => {
resolve('ok-1')
})
let promise2 = new Promise((resolve, reject) => {
resolve('ok-2')
})
// let promise3 = new Promise((resolve, reject) => {
// reject('fail-1')
// })
Promise.all([promise1, promise2, 34]).then((res)=>{
console.log('成功', res)
}, (err)=>{
console.log('失败', err)
})
// 打印结果为:成功 [ 'ok-1', 'ok-2', 34 ]
// promise3放开后: 失败 fail-1
上边了解的promise.all的特性后然后咱们就来看一下源码实现吧:
static all(promises) {
// 将数组中的promise依次执行
const result = []
let count = 0
return new Promise((resolve, reject) => {
const process = (index, value) => {
result[index] = value count++
// 解决多个异步并发问题,只能靠计数器
if (count === promises.length) resolve(result)
}
promises.forEach((promise, index) => {
if (promise && typeof promise.then === 'function') { // 异步的
promise.then(res => {
process(index, res)
}, err => reject(err))
} else { //同步的
process(index, promise)
}
})
})
}
六、Promise.any()
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果有一个Promise成功,则返回这个成功结果
- 如果所有Promise都失败,则报错 老规矩,先上any原生使用方法,看一下执行结果:
let promise1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-1')
}, 1000)
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-2')
}, 500)
})
let promise3 = new Promise((resolve, reject) => {
reject('fail-1')
})
Promise.any([promise1, promise2, promise3, 34]).then((res)=>{
console.log('成功', res)
}, (err)=>{
console.log('失败', err)
})
// 当有非promise项,且其他pormise项有延迟返回时会直接将非promise项返回: 打印结果: 成功 34
// 仅有promise项时,只要有一个promise成功然后返回相应的返回结果:打印结果:成功 ok-2
// 如果全部失败,则会报错: 失败 AggregateError: All promises were rejected
手撸一个any:
static any(promises) {
return new Promise((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
七、Promise.race()
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 返回结果以最快执行完的那个Promise的结果为准,无论成功失败 原生用法:
let promise1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-1')
}, 1000)
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-2')
}, 500)
})
let promise3 = new Promise((resolve, reject) => {
reject('fail-1')
})
Promise.race([promise1, promise2, promise3, 34]).then((res)=>{
console.log('成功', res)
}, (err)=>{
console.log('失败', err)
})
// 返回结果以最快执行完的结果为准: 打印结果 失败 fail-1
race的源码实现:
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
if (promise && typeof promise.then === 'function') {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}
八、Promise.allSettled()
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 把每一个Promise的结果,集合成数组返回,返回的数组里是所有promise及非promise结果的集合
- 它不同于all的是:返回结果不相互依赖 🌰
let promise1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-1')
}, 1000)
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('ok-2')
}, 500)
})
let promise3 = new Promise((resolve, reject) => {
reject('fail-1')
})
Promise.allSettled([promise1, promise2, promise3, 34]).then((res)=>{
console.log('成功', res)
}, (err)=>{
console.log('失败', err)
})
实现源码,展示:
static allSettled(promises) {
return new Promise((resolve, reject) => {
const res = []
let count = 0
const process = (status, value, i) => {
res[i] = { status, value }
count++
if (count === promises.length) {
resolve(res)
}
}
promises.forEach((promise, i) => {
if ((promise && typeof promise.then === 'function') {
promise.then(res => {
process('fulfilled', res, i)
}, err => {
process('rejected', err, i)
})
} else {
process('fulfilled', promise, i)
}
})
})
}
九、Promise.prototype.finally()
- finally是promise无论
成功和失败都会执行的方法 - finally没有参数,他会将promise的状态及结果向下传递 举个🌰
let promise = new Promise((resolve, reject)=>{
resolve('fulfilled')
//reject('rejected')
})
promise.finally(()=>{
console.log('无论成功失败都执行')
}).then((res)=>{
console.log('成功', res)
},(err)=>{
console.log('失败', err)
})
/*
* resolve: 执行结果为
* <1>无论成功失败都执行
* <2>成功 fulfilled
*
* reject: 执行结果为
* <1>无论成功失败都执行
* <2>失败 rejected
*/
finally的实现:
- finally里返回的会是一个then,同时将结果传给下一个then
Promise.prototype.finally = function (cb) {
return this.then((y)=>{
return Promise.resolve(cb()).then((d)=>y);
},(r)=>{
// cb执行一旦报错 就直接跳过后续的then的逻辑,直接将错误向下传递
return Promise.resolve(cb()).then(()=> {throw r})
})
}
测试代码:
Promise.reject('ok').finally(()=>{ // finally 如果返回的是一个promise那么会有等待效果
console.log('无论成功失败都执行')
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('xxxxxxxxxxx'); // 如果是失败 会用这里的失败作为失败的原因
}, 1000);
});
}).then((data)=>{
console.log('成功',data)
}).catch(err=>{
console.log('失败',err)
});
//执行结果为:
// 无论成功失败都执行
// 失败 ok
Promise基本的使用和源码实现已经实现的七七八八了,搞懂这些promise的面试及日常使用基本就没有啥问题了,加油!