概括
new Promise((resolve,reject) => {/* 业务逻辑等 */resolve()}).then(res => {}, err => {})
- 这里的resolve,reject可看作Promise类暴露出来的接口,用来改变promise实例的状态,改变状态后,由then中的两个回调来做处理。执行流程是
立即执行传给Promise的executor -> 同步执行then, 收集then中的回调 -> executor中触发resolve/reject -> 触发所收集的回调
- then中回调都是对前一个promise实例的处理,并返回一个新的promise。
- then的值穿透原理就是,then返回新的promise实例的时候,这个实例内的属性state会被被上一个then中回调函数的结果所改变,继而resolve/reject的时候,触发(已经收集的)下一个then中的回调。
Promise 解决了什么问题?
回调地狱(then的链式调用)、 处理多个异步请求并发(promise.all)。Promise 解决的是异步编码风格的问题,增加可阅读性和可维护性
Promise 的业界实现都有哪些?
bluebird、Q、ES6-Promise Promise 常用的 API 有哪些? Promise.resolve() Promise.reject() Promise.prototype.catch() Promise.prototype.finally() Promise.all() Promise.race()
- 能不能手写一个符合 Promise/A+ 规范的 Promise?
见下文
- Promise 在事件循环中的执行过程是怎样的?
promise.then属于微任务,在下一个事件循环前,会检查是否存在微任务,有则执行。(当前执行栈清空后,检查微任务队列,有则执行)
- Promise 有什么缺陷,可以如何解决?
无法中断
基本实现
结合 Promise/A+ 规范,我们可以分析出 Promise 的基本特征
- promise 有三个状态:
pending,fulfilled,orrejected;「规范 Promise/A+ 2.1」new promise时, 需要传递一个executor()执行器,执行器立即执行;executor接受两个参数,分别是resolve和reject;- promise 的默认状态是
pending;- promise 有一个
value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」- promise 有一个
reason保存失败状态的值;「规范 Promise/A+ 1.5」- promise 只能从
pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;- promise 必须有一个
then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」- 如果调用 then 时,promise 已经成功,则执行
onFulfilled,参数是promise的value;- 如果调用 then 时,promise 已经失败,那么执行
onRejected, 参数是promise的reason;- 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调
onRejected;
// tip: class声明的类中, 默认执行严格模式,this在未明确指定的情况下,指向undefined,而不是window.// this 跟作用域无关,而跟调用执行函数时的执行上下文有关const PENDING = 'PENDING'const FULFILLED = 'FULFILLED'const REJECTED = 'REJECTED'class MyPromise {constructor (executor) {this.state = PENDINGthis.value = undefined // 存放成功的值this.reason = undefined // 存放失败的原因this.onResolveCallbacks = [] // 存放成功的回调this.onRejecedCallbacks = [] // 存放失败的回调let resolve = (value) => { // 箭头函数,this是外层的thisthis.state = FULFILLEDthis.value = valuethis.onResolveCallbacks.forEach(fn => fn()); // 在调用resolve时,将收集的回调触发一遍}let reject = (reason) => {this.state = REJECTEDthis.reason = reasonthis.onRejecedCallbacks.forEach(fn => fn()); // 在调用reject时,同理}try {// 执行传给new Promise的函数,里面的逻辑各种各样,如果代码本身出了问题,也会被try-catch捕获,进而被后续的then或者.catch捕捉到// 这就是很多代码typeError或者referenceError没有在控制台到看到的原因,报错被promise.catch了// executor里代码的逻辑各有需求,但是一旦调用了resolve、reject,就是改变state触发then中收集的回调的时候了executor(resolve, reject)} catch (error) {reject(error)}}then (onFulfilled, onRejected) {if (this.state === FULFILLED) {onFulfilled(this.value)}if (this.state === REJECTED) {onRejected(this.reson)}// 如果没有对pengding时回调的收集,那么将无法在异步调用resolve/reject时候触发onFulfilled/onRejected// 比如下面setTimeout时resolve,由于执行then的时候,state还是pending状态,那么什么都不会执行if (this.state === PENDING) {//这里是一个发布订阅模式,这里收集依赖 -> 在某个时机(调用resolve/reject时)触发通知 -> 取出依赖this.onResolveCallbacks.push(() => onFulfilled(this.value))this.onRejecedCallbacks.push(() => onRejected(this.reson))}}}const promise = new MyPromise((resolve, reject) => {setTimeout(() => {resolve('成功')}, 1000);}).then(res => console.log('res ---', res),error => console.log('error ---', error))
then的链式调用和值穿透特性
promise的优势在于可以链式调用,比如我们在使用promise时,在then中返回了一个值,无论是什么值,我们都能在下一个then中获取到,这就是then的链式调用。而且当我们不在then中放入参数,promise.then().then(),那么其后面的then依旧可以得到之前then的返回值,这就是值的穿透。
这是如何实现的呢?
其实就是在每次调用then的时候,我们都创建一个新的promise对象,将之前的结果传给这个promise.then,那么再结合 Promise/A+ 规范梳理一下思路
- then 的参数
onFulfilled和onRejected可以缺省,如果onFulfilled或者onRejected不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;「规范 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」- promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise”;「规范 Promise/A+ 2.2.7」
- 如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;
- 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」
- 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
- 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」
- 如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」
const PENDING = 'PENDING'const FULFILLED = 'FULFILLED'const REJECTED = 'REJECTED'/** @description: 调用then时的主要逻辑* @param {*} promise2 调用then时返回的新promise* @param {*} x 传入then中的回调执行结果* @param {*} resolve Promise类中resolve函数,触发onFilledCallback* @param {*} reject Promise类中reject函数,触发onRejectedCallback* @return {*}*/const resolvePromise = (promise2, x, resolve, reject) => {// 在promise2中返回了promise2,是个类型错误,需要结束掉 Promise/A+ 2.3.1// 例如 const temp = new Promise((resolve, reject) => {resolve()}).then(() => temp) 返回自己没有意义if (promise2 === x) return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))let lock = false // 只能将状态从pending到fulfilled或者rejected,所以resolve和reject只能调用一个,不能既调resolve又调rejectif ((typeof x === 'object' && x !== null) || typeof then === 'function') {// then中返回的是对象或者是个functiontry {let then = x.thenif (typeof then === 'function') {// 设置x这个对象/方法的then的执行环境then.call(x, res => {if (lock) return// res 本身可能仍然是个方法或对象,递归调用resolvePromise(promise2, res, resolve, reject)}, err => {if (lock) returnlock = truereject(err)})} else {// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4resolve(x)}} catch (err) {reject(err)}} else {resolve(x)}}class Promise {constructor(executor) {this.status = PENDING;this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks= [];let resolve = (value) => {if(this.status === PENDING) {this.status = FULFILLED;this.value = value;this.onResolvedCallbacks.forEach(fn=>fn());}}let reject = (reason) => {if(this.status === PENDING) {this.status = REJECTED;this.reason = reason;this.onRejectedCallbacks.forEach(fn=>fn());}}try {executor(resolve,reject) // 业务逻辑} catch (error) {reject(error)}}then(onFulfilled, onRejected) {//解决 onFufilled,onRejected 没有传值的问题//Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;//因为错误的值要让后面访问到,所以这里也要抛出个错误,不然会在之后 then 的 resolve 中捕获(执行onRejected然后出错,被try-catch然后reject掉)onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };// 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7let promise2 = new Promise((resolve, reject) => {if (this.status === FULFILLED) {//Promise/A+ 2.2.2//Promise/A+ 2.2.4 --- setTimeoutsetTimeout(() => {try {//Promise/A+ 2.2.7.1let x = onFulfilled(this.value);// x可能是一个proimiseresolvePromise(promise2, x, resolve, reject); // 交给resolvePromise去resolve/reject} catch (e) {//Promise/A+ 2.2.7.2reject(e)}}, 0);}if (this.status === REJECTED) {//Promise/A+ 2.2.3setTimeout(() => {try {let x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e)}}, 0);}if (this.status === PENDING) {this.onResolvedCallbacks.push(() => {setTimeout(() => {try {let x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e)}}, 0);});this.onRejectedCallbacks.push(()=> {setTimeout(() => {try {let x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0);});}});return promise2;}}const promise = new Promise((resolve, reject) => {reject('失败');}).then().then().then(data=>{console.log(data);},err=>{console.log('err',err);})
总结,链式操作,就是每次then的时候都返回了一个新的promise。
值穿透就是,每次then的时候,返回了新的promise,而这个promise引用了前一个Promise实例化对象中的属性state,这个state是由then之前的resolve或者reject来修改的,并相应记录了对应的value或者reson。在then的逻辑中,判断onFulfilled(this.value),onRejected(this.reason)的执行结果,如果是promise类,则递归调用,否则调用resolve/reject (这里的resolve/reject是then中new Promise中executor的参数,设置的是新Promise实例的state),而resolve/reject的调用,会将新Promise实例的state设置为fulfilled/reject,并设置对应value、reason,被下一个then调用的时候,重复这个过程。即
then引用着前一个promise实例的state、value、reason等属性,并设置新实例的state以备下一个then调用。
API 实现
Promise.resolve
// 默认产生一个成功的 promisestatic resolve(data){return new Promise((resolve,reject)=>{resolve(data);})}// promise.resolve 是具备等待功能的。如果参数是 promise 会等待这个 promise 解析完毕,在向下执行,所以这里需要在 resolve 方法中做一个小小的处理let resolve = (value) => {// ======新增逻辑======// 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析if(value instanceof Promise){// 递归解析return value.then(resolve,reject)}// ===================if(this.status === PENDING) {this.status = FULFILLED;this.value = value;this.onResolvedCallbacks.forEach(fn=>fn());}}
Promise.reject
// 默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。static reject(reason){return new Promise((resolve,reject)=>{reject(reason);})}
Promise.prototype.catch
// Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。Promise.prototype.catch = function(errCallback){return this.then(null,errCallback)}
Promise.prototype.finally
// finally 表示不是最终的意思,而是无论如何都会执行的意思。// 如果返回一个 promise 会等待这个 promise 也执行完毕。如果返回的是成功的 promise,会采用上一次的结果;如果返回的是失败的 promise,会用这个失败的结果,传到 catch 中。Promise.prototype.finally = function(callback) {return this.then((value)=>{return Promise.resolve(callback()).then(()=>value)},(reason)=>{return Promise.resolve(callback()).then(()=>{throw reason})})}
Promise.all
promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)
// then里记录value,在达到values数组长度时,返回所有的valuesPromise.all = function(values) {if (!Array.isArray(values)) {const type = typeof values;return new TypeError(`TypeError: ${type} ${values} is not iterable`)}return new Promise((resolve, reject) => {let resultArr = [];let orderIndex = 0;const processResultByKey = (value, index) => {resultArr[index] = value;if (++orderIndex === values.length) { // 够数了resolve(resultArr)}}for (let i = 0; i < values.length; i++) {let value = values[i];if (value && typeof value.then === 'function') {value.then((value) => {processResultByKey(value, i); // 存起来}, reject);} else {processResultByKey(value, i);}}});}
Promise.race
Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。
Promise.race = function(promises) {return new Promise((resolve, reject) => {// 一起执行就是for循环for (let i = 0; i < promises.length; i++) {let val = promises[i];if (val && typeof val.then === 'function') {val.then(resolve, reject);} else { // 普通值resolve(val)}}});}
缺陷
因为Promise 是没有中断方法的,xhr.abort()、ajax 有自己的中断方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。
这也是 promise 存在的缺陷,我们可以使用 race 来自己封装中断方法:
function wrap(promise) {// 在这里包装一个 promise,可以控制原来的promise是成功还是失败let abort;let newPromise = new Promise((resolve, reject) => { // defer 方法abort = reject;});let p = Promise.race([promise, newPromise]); // 任何一个先成功或者失败 就可以获取到结果p.abort = abort;return p;}const promise = new Promise((resolve, reject) => {setTimeout(() => { // 模拟的接口调用 ajax 肯定有超时设置resolve('成功');}, 1000);});let newPromise = wrap(promise);setTimeout(() => {// 超过3秒 就算超时 应该让 proimise 走到失败态newPromise.abort('超时了');}, 3000);newPromise.then((data => {console.log('成功的结果' + data)})).catch(e => {console.log('失败的结果' + e)})
promisify
promisify 是把一个 node 中的 api 转换成 promise 的写法。 在 node 版本 12.18 以上,已经支持了原生的 promisify 方法:const fs = require('fs').promises。
const promisify = (fn) => { // 典型的高阶函数 参数是函数 返回值是函数return (...args)=>{return new Promise((resolve,reject)=>{fn(...args,function (err,data) { // node中的回调函数的参数 第一个永远是errorif(err) return reject(err);resolve(data);})});}}// node 中所有的 api 都转换成 promise 的写法const promisifyAll = (target) =>{Reflect.ownKeys(target).forEach(key=>{if(typeof target[key] === 'function'){// 默认会将原有的方法 全部增加一个 Async 后缀 变成 promise 写法target[key+'Async'] = promisify(target[key]);}});return target;}
