经过大家的不懈努力,前端实习生的面试也是癫成了大家看不懂的存在。本人目前大三,在promise还没用明白的年纪,也是要会手写promise了。man~!what can i say.学吧孩子。以下是我对promise的一些拙见,希望能够帮助你我面试成功。
面试中promise的常考形式
- 谈谈你对promise的看法
面对这一类的问题时,留给我们的回答空间很大。但通常面对这一类问题我们又不知从何处开口。这里提供一种思路给大家:先从promise是什么开始,再映射到其他相关问题上。所以就会牵扯出以下几个相关的问题出来。
diff
- 为什么会出现
- 常用的API有哪些
- 手写promise
- promise有什么缺陷
这里一个个给大家好好谈谈。
什么是promise
在promise出现之前,我们在需要连续进行多个异步操作,并且每个操作都依赖于前一个操作完成后才能执行时,是通过不断的回调嵌套来达到目的。而这些代码往往难以阅读和维护,这种情况称之为回调地狱 。所以promise的出现避免了这种情况的发生,也就是说:promise解决了回调地狱的问题
有哪些API
- Promise.resolve
- Promise.reject
- Promise.all
- Promise.race
- Promise.allSettled
- Promise.any
- promise.try
- Promise.prototype.catch
- Promise.prototype.finally
- Promise.prototype.then
这么多API当然不用全都记住,记住几个常用的就行了,可以对这几个常用的进行深入的说明,如何用的以及如何打造的。如果全部方法都能记住当然是加分的。
手写Promise
要会手写,就得知道Promise有哪些功能,清晰的思路能够帮助我们更好的输出。
理解Promise
- 下面这段话你也可以在适当的时候输出给面试官,如果你感觉面试官还挺愿意听你讲的时候时候。
Promise是一个构造函数,是异步编程的一种解决方案,通过Promise对象我们可以获取异步操作的消息。而Promise最主要的特性就是:链式调用 、状态转换 和错误处理。
Promise有三种状态:panding
(进行中)、fulfilled
(已成功)、rejected
(已失败)。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,就不会再变了,会一直保持这个结果。
链式调用指通过.then()
或.catch()
方法将多个Promise操作连接起来,使得每个Promise的解决成功(fulfilled)或失败(rejected)值可以被传递给下一个.then()
或.catch()
方法进行处理。
在Promise中,错误处理通常通过.catch()
方法来实现,它允许你指定一个回调函数来处理Promise链中发生的任何错误,你可以在一个集中的地方处理所有可能出现的错误。
手写,实现一个Promise
这里我们实现一个简单的Promise,包括状态的转换,then()和catch()方法。
kotlin
class myPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallback = [] //then 的回调
this.onRejectedCallback = [] //catch 的回调
// 改变Promise状态为fulfilled,并调用所有成功的回调函数
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
//then 的回调在这里触发
this.onFulfilledCallback.forEach(callback => callback(value))
}
}
// 改变Promise状态为rejected,并调用所有失败的回调函数
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
//catch 的回调在这里触发
this.onRejectedCallback.forEach(callback => callback(reason))
}
}
executor(resolve, reject)
}
// then方法用于注册成功和失败的回调函数
then(onFulfilled, onRejected) {
// 处理默认情况,如果没有提供回调函数,则使用默认函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
const newPromise = new myPromise((resolve, reject) => {
if(this.status === 'fulfilled') {
setTimeout(() => {
try {
const result = onFulfilled(this.value)
resolve(result)
}
catch (error) {
reject(error)
}
}, 0)
}
if(this.status === 'rejected') {
setTimeout(() => { // 模拟异步,但是模拟不了微任务
try {
const result = onRejected(this.reason)
resolve(result)
}
catch (error) {
resolve(error)
}
}, 0)
}
// 如果Promise处于pending状态,则将回调函数保存到队列中
if (this.status === 'pending') {
this.onFulfilledCallback.push(() => {
setTimeout(() => {
try {
const result = onFulfilled()
resolve(result)
} catch (error) {
reject(error)
}
},0)
})
this.onRejectedCallback.push(()=> {
setTimeout(() => {
try {
const result = onRejected()
resolve(result)
} catch (error) {
reject(error)
}
},0)
})
}
})
return newPromise
}
}
Promise/A+规范
一个完整的Promise必须符合以下规范:(Promise/A+规范)
- 状态:一个Promise必须处在以下三种状态之一:pending(等待态)、fulfilled(执行态)或rejected(拒绝态)。Pending状态可以转移到fulfilled或rejected状态,但一旦Promise进入fulfilled或rejected状态,就不可再变。
- 值:当Promise处于fulfilled状态时,它必须持有一个值,这个值被称为"value"。当Promise处于rejected状态时,它必须持有一个原因,这个原因被称为"reason"。
- then方法:Promise对象都有一个then方法,它用于注册两个回调函数,一个用于处理fulfilled状态,另一个用于处理rejected状态。then方法总是返回一个Promise,这使得可以链式调用Promise。
- 错误处理:Promise/A+规范提供了统一的错误处理机制。如果一个Promise被rejected,并且没有提供rejected状态的回调函数,那么错误会被传播到链中的下一个Promise。如果链中的最后一个Promise被rejected,并且没有提供rejected状态的回调函数,那么会调用全局的unhandledrejection事件处理器(如果存在)。
- 解析过程:规范引入了一种叫Promise解析过程的抽象过程,标记为[[Resolve]](promise, x)。这个过程用于将非标准的类Promise接口、对象和函数最终规范成一个标准的Promise。
注意,以下提到的API均写在上面的class中。
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
ini
const p = Promise.all([p1, p2, p3]);
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
实现:
javascript
static all(promises) {
return new myPromise((resolve, reject) => {
let count = 0
let arr = []
// 判断promises中的所有的promise状态是否都为fulfilled
promises.forEach((promise, i) => {
promise.then(
(value) => {
count++
arr[i] = value
if (count === promises.length) {
resolve(arr)
}
},
(reason) => {
reject(reason)
}
)
})
})
}
Promise.any()
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
实现:
javascript
static any(promises) {
return new myPromise((resolve, reject) => {
let count = 0, arr = []
promises.forEach((promise, i) => {
promise.then(
(value) => {
resolve(value)
},
(reason) => {
count++
arr[i] = reason
if (count === promises.length) {
reject(new AggregateError(arr, 'All promises were rejected'))
}
}
)
})
})
}
Promise.prototype.finally()
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
javascript
finally(callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value)
},
(reason) => {
return Promise.resolve(callback()).then(() => reason)
}
)
}
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
ini
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
javascript
static race(promises) {
return new myPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then((value) => {
resolve(value)
},
(reason) => {
reject(reason)
}
)
})
})
}
Promise.allSettled()
Promise.allSettled()
方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled
还是rejected
),返回的 Promise 对象才会发生状态变更。
javascript
static allSettled(promises) {
return new myPromise((resolve, reject) => {
let arr = [], count = 0
promises.forEach((promise, i) => {
promise.then(
(value) => {
arr[i] = {status: 'fulfilled', value: value }
},
(reason) => {
arr[i] = {status: 'rejected', reason: reason }
}
).finally(() => {
count++
// 所有promise状态都变更了
if (count === promises.length) {
resolve(arr)
}
})
})
})
}
关于Promise,一定要理解原理,把常用的API记住是什么用法,这些才是面试中重要的。至于手写......理解之后多写几次应该就可以了,毕竟不多写几次就是很容易忘。至于手写Promise是不是常考题,大家见仁见智吧。