前端面试题之手写promise
前端面试题之Promise问题
前言
在我们日常开发中会遇到很多异步的情况,比如涉及到
网络请求(ajax,axios等)
,定时器
这些,对于这些异步操作我们如果需要拿到他们操作后的结果,就需要使用到回调函数。拿请求来说,如果我们需要拿到请求回来的数据我们就需要利用回调函数(见代码片段1),以下所有的请求都是使用jQuery的ajax模拟。
点击查看代码片段1
// 代码片段1
$.ajax({
url: 'url',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// success就是响应成功的回调函数,在此处可以获取到响应返回的内容
console.log(res)
}
})
在网络请求中会遇到请求之前的依赖,一个请求会依赖于另一个请求的时候,就会需要出现回调嵌套的问题(代码片段2)
点击查看代码片段2
// 代码片段2
// 请求2的请求参数是请求1的响应结果
// 请求3的请求参数是请求2的响应结果
$.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
},
success: function(res){
// res是请求1的响应结果
console.log(res)
$.ajax({
url: '请求2的地址',
type: 'post',
data: {
// 参数1是请求1的响应结果
参数1: res,
参数2: 值2
},
success: function(res2){
// res2是请求2的响应结果
$.ajax({
url: '请求3的地址',
type: 'post',
data: {
// 参数1是请求2的响应结果
参数1: res2,
参数2: 值2
},
success: function(res3){
// res3是最终请求的结果
console.log(res3)
}
})
}
})
}
})
这样的代码就出现了js编程里面的著名
回调地狱问题
,为了解决这个问题我们需要利用es2015的promise和es2016的await来解决(代码片段3)
点击查看代码片段3
// 代码片段3
// await不能写在同步的代码里面,会阻塞整个程序的进程,只能写在异步的代码里面
// async可以修饰一个函数让这个函数变成一个异步的函数
async function getRes(){
// $.ajax() 返回的是一个promise对象
// await可以等待一个promise状态结束,拿到响应的结果
const res1 = await $.ajax({
url: '请求1的地址',
type: 'post',
data: {
参数1: 值1,
参数2: 值2
}
})
const res2 = await $.ajax({
url: '请求2的地址',
type: 'post',
data: {
参数1: res,
参数2: 值2
}
})
const res3 = await $.ajax({
url: '请求3的地址',
type: 'post',
data: {
参数1: res2,
参数2: 值2
}
})
}
// 调用异步函数
getRes()
利用promise和await就可以将之前的回调嵌套地狱改成同步的代码方式,但是这里面大家要注意await的使用事项,await不允许出现在同步代码块里面,会阻塞同步代码执行,必须写在异步的代码块里面,加async让getRes函数成为一个异步的函数,这样getRes执行不会阻塞全局,getRes函数内部代码就是一个同步的方式执行,就解决了回调嵌套的问题。本来故事到这里就该说goodBye了,但是现在很多面试,尤其是一些大厂的面试要求我们自己实现promise和await,这章节先给大家实现promise。
Promise的封装
1. promise的介绍和使用
状态(state)
- pending
- promise的初始状态,并且此状态可以转换成
fulfilled
和rejected
- promise的初始状态,并且此状态可以转换成
- fulfilled
- promise的成功状态,不可以转换其他状态,并且必须有一个不可改变的最终值
value
- promise的成功状态,不可以转换其他状态,并且必须有一个不可改变的最终值
- rejected
- promise的失败状态,不可以转换其他状态,并且必须有一个不可改变的原因
reason
- promise的失败状态,不可以转换其他状态,并且必须有一个不可改变的原因
- pending
术语
解决(fulfill)
当调用resolve方法时promise的状态变成fulfill
拒绝(reject)
当调用reject方法时promise的状态变成reject
终值(value)
所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
拒因(reason)
也就是拒绝(失败)的原因,指在 promise 被拒绝时传递给拒绝回调的值。
面向对象编程方式
- Promise是面向对象编程方式,对应的构造函数是
Promise
- 使用Promise的时候需要创建一个实例对象,使用实例对象的方法
- Promise构造函数实例化的时候需要传递一个函数(handler),此函数里面可以接收两个参数,一个函数叫做resolve,一个函数叫做reject
- 调用resolve时候promise实例的状态变为 fulfilled
- 调用reject时候promise实例的状态变为 rejected
- 此处要注意一个promise实例只有一个状态,也就是不能同时调用resolve和reject
- Promise是面向对象编程方式,对应的构造函数是
点击查看代码
// promise01就是实例化Promise构造函数得到的实例对象
const promise01 = new Promise((resolve,reject)=>{
// 此处调用resolve时promise状态会变成fulfilled
// 此处调用reject时promise状态会变成rejected
})
- 实例对象的方法
- then方法
- 该方法有两个参数,可选,分别对应onFulfilled,onRejected
- 当状态为fulfilled时,调用onFulfilled,得到一个终值 value
- 当状态为rejected时,调用onRejected,得到一个拒因 reason
- catch方法
- 该方法有一个参数,可选,对应onRejected
- 当状态为rejected时,调用onRejected,得到一个拒因
- finally方法
- 该方法有一个参数,可选
- 该方法无论是promise状态成功还是失败都会调用
- 注意:
- 实例方法可以实现链式调用,因为每一次方法调用完成之后都会返回一个promise实例
- then方法可以调用多次
- then方法
点击查看代码
const promise01 = new Promise((resolve,reject)=>{
})
// 使用实例对象方法 链式调用
promise01.then(
// onFulfilled
function(value){
// 当promise实例的状态为fulfilled调用
},
// onRejected
function(reason){
// 当promise实例的状态为rejected调用
}
).then(
// then方法可以调用多次
// 并且调用可以不传递参数
).catch(function(reason){
// 当promise实例的状态为rejected调用
}).finally(function(){
// 无论promise实例的状态为fulfilled或者rejected都会调用
})
2. 实现手写Promise
实现Promise的构造函数
- 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
- handler函数需要两个形式参数 对应的是类自身的两个方法
- resolve方法
- 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
- 调用方法promise的状态变为fulfilled
- 调用该方法会得到一个终值value
- 该方法可能异步调用
- 该方法里面会用到实例的this,在调用的时候需要改变this的指向
- reject方法
- 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
- 调用方法promise的状态变为rejected
- 调用该方法会得到一个拒因reason
- 该方法可能异步调用
- 该方法里面会用到实例的this,在调用的时候需要改变this的指向
- resolve方法
- 实例里面属性
- value 终值
- reason 拒因
- state 状态
- 状态可以作为静态的属性,也可以作为实例的属性
- 状态会多次用于判断,所以建议用常量保存
- 实例里面的方法
- then
- catch
- finally
- 类自身的方法
- resolve
- reject
// promise.js
// 状态会多次用于判断,所以建议用常量保存
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Promise{
constructor(handler){
// 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
if(typeof handler !== 'function'){
throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`)
}
this.state = PENDING // 用来存储promise的状态 初始化状态为pending
this.reason = undefined // 拒因
this.value = undefined // 终值
// resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向
handler(this.resolve.bind(this), this.reject.bind(this))
}
resolve(value){
// 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
if(this.state !== PENDING) return
// 调用方法promise的状态变为fulfilled
this.state = FULFILLED
// 调用该方法会得到一个终值value
this.value = value
}
reject(reason){
// 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
if(this.state !== PENDING) return
// 调用方法promise的状态变为rejected
this.state = REJECTED
// 调用该方法会得到一个拒因reason
this.reason = reason
}
}
// 试错
const p = new Promise(1)
console.log(p)
// promise.js:9 Uncaught TypeError: Promise构造函数的参数1不是一个函数
// 正确操作
// 状态成功
const p = new Promise((resolve, reject) => {
resolve()
})
console.log(p)
// Promise {state: 'fulfilled', reason: undefined, value: undefined}
// 状态失败
const p = new Promise((resolve, reject) => {
reject()
})
console.log(p)
// Promise {state: 'rejected', reason: undefined, value: undefined}
实现实例then方法
then方法接受两个参数,两个参数类型是函数
- onFulfilled
- 需要对onFulfilled类型进行验证,如果不是一个函数就设置为默认函数
function(value){return value}
,这个函数是获取终值 - 该函数在promise状态为fulfilled时候调用,传入终值
- 需要对onFulfilled类型进行验证,如果不是一个函数就设置为默认函数
- onRejected
- 需要对onRejected类型进行验证,如果不是一个函数就设置为一个默认函数
function(err){throw err}
,这个函数是获取拒因 - 该函数在promise状态为rejected时候调用,传入拒因
- 需要对onRejected类型进行验证,如果不是一个函数就设置为一个默认函数
// promise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Promise{
constructor(handler){...}
resolve(value){...}
reject(reason){...}
then(onFulfilled, onRejected){
// 参数可选要进行判断 如果传递不是一个函数 默认一个函数
onFulfilled = typeof onFulfilled==='function' ? onFulfilled : value => value;
onRejected = typeof onRejected==='function' ? onRejected : err => {throw err};
if(this.state === FULFILLED){
// 当promise状态为fulfilled时候调用onFulfilled
onFulfilled(this.value)
}
if(this.state === REJECTED){
// 当promise状态为rejected时候调用onRejected
onRejected(this.reason)
}
}
}
// test.js
// 成功状态
const p = new Promise((resolve, reject) => {
resolve('success')
})
p.then(
res => {
console.log('调用了onFullfilled')
console.log(res)
},
err => {
console.log('调用了onRejected')
console.log(err)
}
)
// 控制台输出 调用了onFullfilled sucess // 失败状态
const p = new Promise((resolve, reject) => {
reject('error')
})
p.then(
res => {
console.log(res)
},
err => {
console.log(err)
}
)
// 控制台输出 调用了onRejected error // handler函数异步调用resolve
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('success')
},3000)
})
p.then(
res => {
console.log('调用了onFullfilled')
console.log(res)
},
err => {
console.log('调用了onRejected')
console.log(err)
}
)
// 控制台没有任何内容输出 onFulfilled函数并没有被调用
- 此时经过测试,目前的promise可以正常的调用,此时针对于同步的调用没有问题,但是在
handler
里面resolve
和reject
大部分情况下都是异步调用的,比如说延迟3s调用,此时then里面的onFulfilled
和onRejected
是无法执行的,因为then函数在执行的时候promise的状态是pending,onFulfilled
和onRejected
函数里面是做了判断,没有对pendng状态进行处理,针对于pending状态的需要处理
// promise.js
// ...
class Promise {
// 用来存储状态成功的回调函数
// 用来存储状态失败的回调函数
successCallBack = null
errorCallBack = null
constructor(handler) {...}
resolve(value) {
// ...
// 异步调用的时候执行存储的onFulfilled函数,传入终值
this.successCallBack(this.value) }
reject(reason) {
// ...
// 异步调用的时候执行存储的onRejected函数,传入拒因
this.errorCallBack(this.reason)
}
then(onFulfilled, onRejected) {
// ...
if (this.state === PENDING) {
// 当resolve或者reject异步调用,then执行的时候promise状态等待
// 将onFulfilled和onReject函数存储起来
this.successCallBack = onFulfilled
this.errorCallBack = onRejected
}
}
}
// test.js
// 异步调用resolve
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('success')
},3000)
})
p.then(
res => {
console.log('调用了onFullfilled')
console.log(res)
},
err => {
console.log('调用了onRejected')
console.log(err)
}
)
// 3s后控制台输出 调用了onFullfilled success
- 此时promise的handler就可以异步调用
resolve
或reject
,then里面可以获取到终值或据因 - then函数支持多次调用,所以此时then的回调函数就不止一个,我们将之前的变量改成数组用来存储then的回调函数
// promise.js
// ...
class Promise {
// 用数组的方式存储失败和成功的回调函数
successCallBack = []
errorCallBack = []
constructor(handler) {...}
resolve(value) {
// ...
// 遍历数组将所有成功的函数执行
this.successCallBack.forEach(fn=> fn())
}
reject(reason) {
// ...
// 遍历数组将所有失败的函数执行
this.errorCallBack.forEach(fn=>fn())
}
then(onFulfilled, onRejected) {
// ...
if (this.state === PENDING) {
// 当resolve或者reject异步调用,then执行的时候promise状态等待
// 将onFulfilled和onReject函数存储起来
this.successCallBack.push(()=>{
onFulfilled(this.value)
})
this.errorCallBack.push(()=>{
onRejected(this.reason)
})
}
}
}
- 测试调用了两次then,状态成功之后两次then的成功回调都会被执行
// test.js
// 异步调用resolve
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('success')
},3000)
})
p.then(
res => {
console.log('调用了onFullfilled')
console.log(res)
},
err => {
console.log('调用了onRejected')
console.log(err)
}
)
p.then(
res => {
console.log('调用了onFullfilled01')
console.log(res)
},
err => {
console.log('调用了onRejected01')
console.log(err)
}
)
// 3s后控制台输出 调用了onFullfilled success 调用了onFullfilled01 success
- 但是目前then不支持链式调用,需要进行链式调用的支持
- 支持链式调用的条件
- 只有then函数调用返回一个promise对象,一个新的promise对象
newPromise
- then函数有一个返回值 这个返回值
result
就是新promise的onFulfill或onRejected的值
- 只有then函数调用返回一个promise对象,一个新的promise对象
- 接下来我们需要判断这个返回值
result
的类型- 一个具体值
- 一个新的promise
- 另外我们需要一个函数去处理这种链式调用的问题
handlerChainPromise
,这个函数接收参数如下- result 老的promise的then返回值,作为新promise的onFulfill或onRejected
- resolve成功处理函数
- reject失败处理函数
class Promise {
then(onFulfilled, onRejected) {
// 每一次调用返回一个promise
return new Promise((resolve,reject) => {
// 参数可选要进行判断 如果传递不是一个函数 默认一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
if (this.state === PENDING) {
// 当resolve或者reject异步调用,then执行的时候promise状态等待
// 将onFulfilled和onReject函数存储起来
this.successCallBack.push(() => {
let result = onFulfilled(this.value)
this.handlerChainPromise(result, resolve, reject)
})
this.errorCallBack.push(() => {
let result = onRejected(this.reason)
this.handlerChainPromise(result, resolve, reject)
})
}
if (this.state === FULFILLED) {
// 当promise状态为fulfilled时候调用onFulfilled
let result = onFulfilled(this.value)
this.handlerChainPromise(result, resolve, reject)
}
if (this.state === REJECTED) {
// 当promise状态为rejected时候调用onRejected
let result = onRejected(this.reason)
this.handlerChainPromise(result, resolve, reject)
}
})
}
handlerChainPromise(result,resolve,reject){
// 如果返回的是一个promise就调用它的then方法
// 如果返回的是一个具体值就直接返回值
if(result instanceof Promise){
result.then(resolve, reject)
}else{
resolve(result)
}
}
}
- 测试代码如下
// 返回一个具体值(可以使任何类型)
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('success')
},3000)
})
p.then(
res => {
return 11
},
err => {
console.log(err)
}
).then(
res=>{
console.log(res)
}
)
// 返回一个promsie
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('success')
},3000)
})
p.then(
res => {
return new Promise(resolve=>{
setTimeout(()=>{
resolve(22)
}, 1000)
})
},
err => {
console.log(err)
}
).then(
res=>{
console.log(res)
}
)
- 以上一个完整的promise就封装成功了
- 完整代码如下
// 状态会多次用于判断,所以建议用常量保存
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Promise {
// 用来存储状态成功的回调函数
// 用来存储状态失败的回调函数
successCallBack = []
errorCallBack = []
constructor(handler) {
// 构造函数需要参数,参数必须是一个函数类型 这里面我们叫做handler,这儿会对handler类型进行验证,如果不是函数类型会抛出类型异常
if (typeof handler !== 'function') {
throw new TypeError(`Promise构造函数的参数${handler}不是一个函数`)
}
this.state = PENDING // 用来存储promise的状态 初始化状态为pending
this.reason = undefined // 拒因
this.value = undefined // 终值
// resolve,reject方法里面会用到实例的this,在调用的时候需要改变this的指向
handler(this.resolve.bind(this), this.reject.bind(this))
}
resolve(value) {
// 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
if (this.state !== PENDING) return
// 调用方法promise的状态变为fulfilled
this.state = FULFILLED
// 调用该方法会得到一个终值value
this.value = value
// 异步调用的时候执行存储的onFulfilled函数,传入终值
this.successCallBack.forEach(fn => fn())
}
reject(reason) {
// 该方法只能在promise状态为pending的时候调用,如果promise已经有了状态,则不允许调用
if (this.state !== PENDING) return
// 调用方法promise的状态变为rejected
this.state = REJECTED
// 调用该方法会得到一个拒因reason
this.reason = reason
// 异步调用的时候执行存储的onRejected函数,传入拒因
this.errorCallBack.forEach(fn => fn())
}
then(onFulfilled, onRejected) {
// 每一次调用返回一个promise
return new Promise((resolve,reject) => {
// 参数可选要进行判断 如果传递不是一个函数 默认一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
if (this.state === PENDING) {
// 当resolve或者reject异步调用,then执行的时候promise状态等待
// 将onFulfilled和onReject函数存储起来
this.successCallBack.push(() => {
let result = onFulfilled(this.value)
this.handlerChainPromise(result, resolve, reject)
})
this.errorCallBack.push(() => {
let result = onRejected(this.reason)
this.handlerChainPromise(result, resolve, reject)
})
}
if (this.state === FULFILLED) {
// 当promise状态为fulfilled时候调用onFulfilled
let result = onFulfilled(this.value)
this.handlerChainPromise(result, resolve, reject)
}
if (this.state === REJECTED) {
// 当promise状态为rejected时候调用onRejected
let result = onRejected(this.reason)
this.handlerChainPromise(result, resolve, reject)
}
})
}
handlerChainPromise(result,resolve,reject){
// 如果返回的是一个promise就调用它的then方法
// 如果返回的是一个具体值就直接返回值
if(result instanceof Promise){
result.then(resolve, reject)
}else{
resolve(result)
}
}
}
- onFulfilled
前端面试题之手写promise的更多相关文章
- 前端面试题整理——手写AJAX
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 前端面试题整理——手写方法解析URL参数
//拆分字符串形式 function queryToObj() { const res = {} const search = location.search.substr(1);//去掉前面的&qu ...
- 前端面试题整理——手写简易jquery
class jQuery { constructor(selector) { const result = document.querySelectorAll(selector) console.lo ...
- 前端面试题整理——手写bind函数
var arr = [1,2,3,4,5] console.log(arr.slice(1,4)) console.log(arr) Function.prototype.bind1 = functi ...
- 前端面试题整理——手写flatern摊平数组
// flatern 是摊平数组 function flat(arr) { const isDeep = arr.some(item => item instanceof Array) if(! ...
- 字节跳动-前端面试题 Multi Promise Order
字节跳动-前端面试题 Multi Promise Order Promise Order Async/Await async function async1 () { console.log('asy ...
- 前端面试题(JavaScript)
(前端面试题大全,持续更新) 箭头函数特点?箭头函数和普通函数的区别 手写懒加载(考虑防抖和重复加载问题) 手写bind(为什么要加预参数,为什么要加new) apply, call, bind ne ...
- 金三银四,磨砺锋芒;剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上
金三银四,磨砺锋芒:剑指大厂,扬帆起航(2020年最全大厂WEB前端面试题精选)上 引言 元旦匆匆而过,2020年的春节又接踵而来,大家除了忙的提着裤子加班.年底冲冲冲外,还有着对于明年的迷茫和期待! ...
- 金三银四求职季,前端面试题小梳理(HTML、CSS、JS)
好久没写学习记录,最近太多事,又到一年求职季,都说金三银四求职季,自己也做一下最近学习的一些前端面试题梳理,还是个小白,写的不对请指正,不胜感激. HTML篇 html语义化 用语义化的代码标签书写, ...
随机推荐
- 使用 & 进行高效率取余运算
Java的HashMap源码中用到的(n-1)&hash这样的运算,这是一种高效的求余数的方法 结论:假设被除数是x,对于除数是2n的取余操作x%2n,都可以写成x&(2n-1),位运 ...
- Qt5获取系统文件图标,文件路径
获取系统图标: QFileIconProvider icon_provider; QIcon icon = icon_provider.icon(QFileIconProvider::Folder); ...
- adb shell 查看当前与用户交互的 activity
adb shell dumpsys activity activities | grep mActivityComponent
- noip模拟36
\(\color{white}{\mathbb{荷花映日,莲叶遮天,名之以:残荷}}\) 今天再次翻车掉出前十 开题看错 \(t1\) 以为操作2的值固定发现是个简单题,然后 \(t2\) 开始大力 ...
- Python习题集(五)
每天一习题,提升Python不是问题!!有更简洁的写法请评论告知我! https://www.cnblogs.com/poloyy/category/1676599.html 题目 打印99乘法表 解 ...
- 对easyui-validatebox的验证类型的扩展
easyui为我们提供了validatebox类型的组件,使用它可以完成自动验证,十分方便.要注意的是,easyui中的各个组件都是有继承关系的.通过查看api,textbox继承validatebo ...
- Java字符串常量池及字符串判等解析
一.理解"=="的含义 "=="常用于两个对象的判等操作,在Java中,"=="主要有以下两种用法: 1.基础数据类型:比较的是他们的值是否 ...
- DHCP的原理和配置
前言 在大型企业网络中,会有大量的主机或设备需要获取IP地址等网络参数.如果采用手工配置,工作量大且不好管理,如果有用户擅自修改网络参数,还有可能会造成 IP地址冲突等问题.使用动态主机配置协议DHC ...
- Java基础系列(38)- 数组的使用
数组的使用 For-Each循环 数组作方法入参 数组作返回值 For-Each循环 普通型 package array; import sun.security.util.Length; publi ...
- CF710F-String Set Queries【AC自动机,二进制分组】
正题 题目链接:https://www.luogu.com.cn/problem/CF710F 题目大意 \(T\)次操作 往集合中加入一个字符串 往集合中删除一个字符串 给出一个模式串求出现的集合里 ...