概括
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 = PENDING
this.value = undefined // 存放成功的值
this.reason = undefined // 存放失败的原因
this.onResolveCallbacks = [] // 存放成功的回调
this.onRejecedCallbacks = [] // 存放失败的回调
let resolve = (value) => { // 箭头函数,this是外层的this
this.state = FULFILLED
this.value = value
this.onResolveCallbacks.forEach(fn => fn()); // 在调用resolve时,将收集的回调触发一遍
}
let reject = (reason) => {
this.state = REJECTED
this.reason = reason
this.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又调reject
if ((typeof x === 'object' && x !== null) || typeof then === 'function') {
// then中返回的是对象或者是个function
try {
let then = x.then
if (typeof then === 'function') {
// 设置x这个对象/方法的then的执行环境
then.call(x, res => {
if (lock) return
// res 本身可能仍然是个方法或对象,递归调用
resolvePromise(promise2, res, resolve, reject)
}, err => {
if (lock) return
lock = true
reject(err)
})
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
resolve(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.4
onFulfilled = 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.7
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
//Promise/A+ 2.2.2
//Promise/A+ 2.2.4 --- setTimeout
setTimeout(() => {
try {
//Promise/A+ 2.2.7.1
let x = onFulfilled(this.value);
// x可能是一个proimise
resolvePromise(promise2, x, resolve, reject); // 交给resolvePromise去resolve/reject
} catch (e) {
//Promise/A+ 2.2.7.2
reject(e)
}
}, 0);
}
if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() => {
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
// 默认产生一个成功的 promise
static 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数组长度时,返回所有的values
Promise.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中的回调函数的参数 第一个永远是error
if(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;
}