前言
在网上阅读过很多关于实现promise的文章,大致分为2类,一种是基于promiseA+规范,一种是基于ECMA规范和v8引擎下的promise,对于A+规范可以快速让我们理解promise的核心,但对些许情况,难以理解,而ECMA规范的promise却很难理解,本文会分别对其经行分析
给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
1.基于PromiseA+规范
1.1 前景概要 :
首先要了解promiseA+规范只是社区对于开发者实现promise提出的一个合理的规范而已,它与ECMA规范下的promise(浏览器下的)有很多区别,但是可以满足大部分工作以及学习需求,而且理解比较简单,我们就来简单实现一个promise以及梳理它的流程
let p1 = new Promise((resolve, reject) => { resolve('成功') }) 复制代码
1.2 Promise是构造函数
这是promise的起点,通过形式可以看到 promise属于构造函数 我们需要通过new关键字来调用,内部接收一个回调函数(我们采用executor代理),内部有2个参数resolve,reject分别是2个回调函数,各携带一个参数,所以我们的雏形来了
class MyPromise { constructor(executor) { const resolve = (value) => {} const reject = (reason) => {} executor(resolve, reject) } } 复制代码
继续通过一段代码了解promise
1.3 Promise的状态变更
let p1 = new Promise((resolve, reject) => { resolve('成功') reject('失败') }) p1.then( (value) => { console.log(value) //成功 }, (err) => { console.log(err) } ) let p2 = new Promise((resolve, reject) => { reject('失败') resolve('成功') }) p2.then( (value) => { console.log(value) }, (err) => { console.log(err) //失败 } ) 复制代码
通过上述代码可以知道Promise返回一个实例,并且实例带有then方法,且then方法中包含2个回调函数,(我们以onFulfilled和onRejected代替)可以通过回调函数的参数获取,我们可以通过resolve 和reject函数传递结果,并且通过then里面的回调函数接收对应的结果,而且promise会通过resolve,reject确定状态,一旦确定好状态,就只执行对应的回调函数,忽略其他的resolve或者reject
因此我们需要来指定状态,并且存储resolve,reject的值,从而传递给then
const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED'] class MyPromise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason } } executor(resolve, reject) } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reaosn) } } } 复制代码
上述代码,我们声明了PENDING(等待),FULFILLED(成功),REJECTED(失败)三个状态,来记录promise的状态,并且当resolve或者reject时,我们立即修改状态,并且将成功或者失败的值存储起来。在then的回调函数中通过状态判断来执行对应的回调函数
1.4 解决异步
但是promise是用来解决异步问题的,我们的代码全部是同步执行,还有很多缺陷,例如:
const p1 = new Promise((resolve, reject) => { setTimeout(function () { resolve('成功') }) },3000) p1.then((value) => { console.log(value) }) 复制代码
正常情况下会等待3秒确定状态,然后执行对应then的回调函数,但是我们的代码却不会执行,因为刚才也说过我们的代码全部都是同步执行,没有对PENDING状态进行处理,因此我们需要额外对pending状态进行处理 代码如下
const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED'] class MyPromise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onFulfilled && this.onFulfilled(value) } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejected && this.onRejected(reason) } } executor(resolve, reject) } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reaosn) } if (this.status === PENDING) { // 存储回调函数 this.onFulfilled = onFulfilled this.onRejected = onRejected } } } 复制代码
我们在pending状态下将then的回调函数存储下来,在status改变状态后立即执行达到支持异步的效果
1.5 then的微任务?
我们在通过一个例子来完善代码
let p1 = new Promise((resolve, reject) => { resolve('成功') }) p1.then((value) => console.log(value)) console.log(11) // 11 => 成功 复制代码
通过上述代码,以及promise的知识我们应该知道then的回调函数实际是将这个回调加入到了微任务队列中 所以先打印11 然后再打印成功,而我们的代码却并是同步执行,我们需要将then的回调函数模拟微任务的形式,这里我们使用setTimeout来模拟微任务,修改我们的代码
then(onFulfilled, onRejected) { if (this.status === FULFILLED) { setTimeout(() => { onFulfilled(this.value) }) } if (this.status === REJECTED) { setTimeout(() => { onRejected(this.reason) }) } if (this.status === PENDING) { this.onFulfilled = onFulfilled this.onRejected = onRejected } } 复制代码
1.6存储处理函数的数据结构
这样就可以解决上述then的回调要进入微任务的情况, 下一个我们要解决的问题是promise多次调用的问题
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('成功') }) }) p1.then((value) => { console.log(value,111) }) p1.then((value) => { console.log(value,222) }) 复制代码
上述代码会依次打印成功,但是我们的代码不具备这种条件因为我们的then方法中的onFulfilled会覆盖第一个then的方法的OnFulfilled 这个问题也比较好解决,我们只需要通过一个数组将函数存储起来,到时候遍历调用即可 此时完整代码如下
const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED'] class MyPromise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onFulfilledCallbacks.forEach((fn) => fn()) } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejectedCallbacks.forEach((fn) => fn()) } } executor(resolve, reject) } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { setTimeout(() => { onFulfilled(this.value) }, 0) } if (this.status === REJECTED) { setTimeout(() => { onRejected(this.reason) }, 0) } if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(() => { onRejected(this.reason) }) } } } 复制代码
接下来我们放松一下,处理点小问题,
1.7 trycatch的引用
let p1 = new MyPromise((resolve, reject) => { throw new Error('我要报错') }) p1.then( (value) => { console.log(value, 111) }, (err) => { console.log(err) } ) 复制代码
在executor函数中如果报错,如果我们指定了then方法的接收函数的话,promise将其定义为REJECTED状态, 那我们只需要简单的try/catch进行处理下,遇到错误直接reject就完事了(其实其中大有文章,末尾发链接)
try { executor(resolve, reject) } catch (e) { reject(e) } 复制代码
好了放松完了,我们来点刺激的
2.Promise的链式调用
promise的核心以及最大特点就是链式调用,比如then回调函数的返回值会包裹成一个promise
promiseA+规范 2.27明确说明then方法必须返回一个promsie,并且onfulfilled或者onRejected返回值需要再次进行处理(the Promise Resolution Procedure),如果出现异常我们需要reject出去
then(onFulfilled, onRejected) { let promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { try { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) this.onRejectedCallbacks.push(() => { try { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) } }) return promise2 } 复制代码
我们按照规范分别对onFulfilled以及OnRejected的函数返回值做了处理(ResolvePromise后面再提作用) 也做错了异常检测 2.27.3 与2.27.4是对then的穿透做处理比较简单 如果onfulfiled不是一个函数并且这个promise的状态是fulfilled,返回值promise2必须指定为一个fulfilled的函数并返回上一个then返回的相同的值 如果onrejected不是一个函数并且这个promsie的状态是rejected,我们只需要将这个rejected的的错误继续抛出即可
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value onRejected = typeof onRejected ==='function' ? onRejected :reason=>{throw reason} 复制代码
这样就简单实现了then的穿透,但是只能验证rejected的情况,需要将resolvePromise函数完成才能达到效果
2.1对then的返回值的封装(resolvePromsie)
我们直接从规范2.3.1开始
如果这个promise与返回值x相等,则需要reject这个类型错误 类似于这种情况:
let p1 = new Promise((resolve, reject) => { resolve('成功') }).then((value) => { return p1 // Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> }) 复制代码
那么我们开始封装resolvePromise
function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>')) } } 复制代码
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二):https://developer.aliyun.com/article/1413945