这里主要记录了promise的常考问题(在最后给出了promise的手写),根据聊promise的思路简单记了记,可能有地方聊错了,或者有知识点遗漏,欢迎大家讨论纠正。
1. 了解 promise 吗?
了解
- 首先promise是一种异步编程解决方案,有三种状态,pending(进行中)、resolved(已完成)、rejected(已失败),当promise的状态由pending转为resolved或rejected时,会执行对应的方法。状态一旦发生改变,就无法返回。
- Promise的提出主要就是为解决回调地狱。
2. promise 解决的痛点是什么?
- 回调地狱
- 支持多并发的请求(promise.all)
- 解决了可读性差的代码问题
- promise 解决了代码的信任问题,promise 只有一次决议(状态只有一个)
3. promise 解决的痛点还有其他解决方法吗?
setTimeout 缺点(它只能保证在一段时间后将一段代码加入到任务队列中去,并不能保证在合适的时间将这段代码执行)
async、await 缺点(async、await没有错误捕捉机制,需要手动写try/catch)
generator 缺点(虽然可以将异步函数的整体表示得很简洁(疯狂 yield 接异步),但是整体流程管理不方便,需要手动控制流程)
回调函数
4. promise 如果使用
- 创建 promise 的实例对象
- 用 .then 方法指定 resolved 状态和 rejected 状态的回调函数
- 用 catch 方法指定 rejected 状态的回调函数
5. promise 存在的问题?解决方法?
-
promise 一旦执行,无法中途取消
-
promise 的错误无法在外部捕捉到
- 借助try catch实现错误捕捉
-
promise 的内部如何运行很难被监测(因为promise 的封装算是蛮封闭的了)
- 解决办法
async、await
6.老旧的浏览器没有 promise 全局对象怎么办?
用轮子 es6-polufill 可以用一个页面标签引入,或者用ES6的 import 去引入这个插件,
只要引入这个后,它就会在 window 中加入 promise 对象,这样就可以全局使用 promise 了.
否则就只能用其他异步方法去实现相同功能了。
7. 怎么让一个函数无论 promise 对象成功还是失败都能被调用
Promise.finally()
8. 分别介绍一下Promise.all() Promise.allSettled() Promise.race()方法
Promise.all() 将多个promise实例,包装成一个新的promise实例 它的执行结果方面:传入的实例执行结果全为resolve则返回resolve给调用者的成功回调,反之一次全错,直接调用失败回调。 Promise.allSettled() 只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更,一旦发生状态变更,状态总是fulfilled,不会变成rejected。
> 要将它与all方法区分开来,all会在一个发生错误时,就会执行reject,而allSettled会等到全部执行完毕,并且一定是成功回调
Promise.race() race 方法,返回的是一个Promise对象,由第一个返回的Promise方法决定的 这个方法主要用来优化,例如:两种方法请求数据,哪种方法先返回数据,就拿着数据执行接下来的逻辑
9. promise的实现(拓展)
其实面试的时候不会叫你完整的写一个promise,只是说写这个能加深自己对于promise的理解。
注: 面试方面(常考点):手写一个Promise.all()、Promise.then()的实现思路
(function (window) { //写一个自执行函数,将这个 Promise 挂在 window 上
function myPromise(execotor) { // execotor 执行器函数
//注意看实例对象的使用,会发现 execotor 就是Promise里的箭头函数
// 绑定作用域
let self = this
// 开关变量,记录 Promise执行到何种状态
self.status = 'pending'
//用来存储 resolve 的结果
self.data = undefined
//用来放上 一个又一个的 {resolve =>{},reject =>{}}
self.callbacks = []
// 延时绑定 延时执行!!!!!!!!!!
// 因为promise函数对象的特点是:如果对象还没有返回状态(resolve/reject)的时候,会将then里的后续代码保存,只要返回了状态,就会执行状态对应的相关代码
// 向 Promise 的原型对象上添加方法
function resolve(value) {
if (self.status !== 'pending') {
//说明状态已经改变
return
}
// 将状态改变成为 resolved
self.status = 'resolved'
// 存值
self.data = value
// 注意!!!! .then里面的resolve是一个箭头函数!不会自动调用!
// 如果有待执行的 callback函数,立即 异步执行 它
if (self.callbacks.length > 0) {
// 这里用一个setTimeout简单实现一下异步执行
setTimeout(() => {
self.callbacks.forEach(callbackObj => { //callbackObj 拿到的应该是这种对象 {resolve =>{},reject =>{}}
callbackObj.onResolved(value) //执行第一个代指resolve的函数
})
}, 0)
}
// 接下来就要等着 .then()的执行
}
//reject大体和resolve是一样的
function reject(value) {
if (self.status !== 'pending') {
return
}
// 将状态改变成为 rejected
self.status = 'rejected'
// 存值
self.data = value
// 注意!!!! .then里面的 rejected 是一个箭头函数!不会自动调用!
// 如果有待执行的 callback函数,立即 异步执行 它
if (self.callbacks.length > 0) {
// 这里用一个setTimeout简单实现一下异步执行
setTimeout(() => {
self.callbacks.forEach(callbackObj => { //callbackObj 拿到的应该是这种对象 {resolve =>{},reject =>{}}
callbackObj.onRejected(value) //执行第一个代指 rejected 的函数
})
}, 0)
}
}
// 构造器函数
// 如果在执行过程中,代码出现问题保存,那么该Promise对象会直接变成 rejected 状态
// 执行器里面使用try catch 进行捕获错误
try {
execotor(resolve, reject)
} catch (error) {
reject(error)
}
}
// Promise 原型上挂载各种方法
myPromise.prototype.then = function (onResolved, onRejected) { //接收两个参数 onResolved函数处理成功情况,onRejected函数 处理失败情况
// 不能直接简单粗暴的push方法进去
// onResolved onRejected 存到数组
// this.callbacks.push({ onResolved, onRejected })
// 考虑执行到 .then 的时候,Promise的状态还是pending,
let self = this
// 注意!这里这样写是因为 .then 要返回一个Promise对象
return new myPromise((resolve, reject) => {
if (self.status === 'pending') {
this.callbacks.push({ // 延时绑定 延时执行!!!!!
onResolved() { onResolved(self.data) },
onRejected() { onRejected(self.data) }
})
} else if (self.status === 'resolved') {
// 实现异步执行
setTimeout(() => {
const result = onResolved(self.data)
// 判断.then返回的是不是一个Promise对象
if (result instanceof myPromise) {
//说明回调是一个Promise对象
//实现.then()的链式调用,启后
result.then(
//这里面对本次.then的resolve的调用说明了 承前
value => { resolve(value) },
reason => { reject(reason) }
)
} else {
resolve(result)
}
}, 0)
} else {
setTimeout(() => {
onRejected(self.data)
}, 0)
}
})
}
//一些挂载在Promise原型上的其他方法
myPromise.prototype.catch = function (onRejected) {
}
// Promise 函数对象上挂载方法
//上面的resolve和reject代指的是promise语法里的状态,而下面的则是promise对象上的具体方法
//效果:将一个现有对象转化为Promise对象
myPromise.myResolve = function (value) {
// resolve方法的参数有三种可能:promise对象、thenable对象(带有then方法的对象)、其他
// 是Promise实例,直接返回即可
if (value && typeof value === 'object' && (value instanceof Promise)) {
return value
}
// 否则其他情况一律再通过Promise包装一下
// 注:thenable情况下,会将传入对象转为promise对象,并立即执行其then方法
return new Promise((resolve) => {
resolve(value)
})
}
// reject实现相对简单,只要返回一个新的Promise,并且将结果状态设置为拒绝就可以
myPromise.myReject = function (value) {
return new Promise((_, reject) => {
reject(value)
})
}
//all方法, 将多个 Promise 实例,包装成一个新的 Promise 实例
//全为resolve则返回resolve给调用者的回调函数,反之一个错则全错
myPromise.myAll = function (value) {
return new myPromise((rs, rj) => {
// 计数器
let count = 0;
// 存放结果
let result = [];
const len = value.length
if (len === 0) {
return resolve([])
}
value.forEach((p, i) => {
// 注意有的数组项可能不是promise,需要手动转化一下
Promise.resolve(p).then((res) => {
count += 1
// 收集每个Promise的返回值
result[i] = res
// 当所有的promise都成功了,那么将返回的promise结果设置为result
if (count === len) {
rs(result)
}
}).catch(rj)
// 监听数组项中的Promise catch只要有一个失败,那么我们自己返回的promise也会失败
})
})
}
// allSettled,只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),
//返回的 Promise 对象才会发生状态变更,一旦发生状态变更,状态总是fulfilled,不会变成rejected
//要将它与all方法区分开来,all会在一个发生错误时,就会执行reject,而allSettled会等到全部执行完毕,并且一定是成功回调
myPromise.myAllSettled = function (value) {
//在上面all上微改
return new Promise((rs, rj) => {
let count = 0
let result = []
const len = value.length
// 数组是空的话,直接返回空数据
if (len === 0) {
return rs([])
}
value.forEach((p, i) => {
Promise.resolve(p).then((res) => {
count += 1
// 成功属性设置
result[i] = {
status: 'fulfilled',
value: res
}
if (count === len) {
rs(result)
}
}).catch((err) => {
count += 1
// 失败属性设置
result[i] = {
status: 'rejected',
reason: err
}
if (count === len) {
rs(result)
}
})
})
})
}
// race 方法,返回的是一个Promise对象,由第一个返回的Promise方法决定的
// 这个方法主要用来优化,例如:两种方法请求数据,哪种方法先返回数据,就拿着数据执行接下来的逻辑
myPromise.myRace = function (value) {
return new Promise((rs,rj)=>{
value.forEach(p=>{
// 对p进行一次包装,处理非promise对象
// 对其进行监听,一旦成功就执行then,并且执行我们的成功回调
//这样就实现了,调用者的状态和先执行完的promise对象之前的关联
Promise.resolve(p).then(rs).catch(rj)
})
})
}
// 向外面暴露这个Promise , 这样别的地方都能用 Promise
window.myPromise = myPromise
})()