1.引言
通过手写符合A+规范的promise,来深入了解Promise,再结合相关面试题,争取做到在面试的时候,如果问Promise,咱们能全方位吊打面试官??? 下面的每一个写法都对应Promise的一些特性,不断升级,了解原理后再做题就会发现很简单了
2.极简版promise
2.1 基础特性
详细介绍的话大家去看 阮一峰es6-promise,我这里当你已经有一定的基础了,然后我们总结一下基本特性
new Promise((resolve,reject)=>{ //excutor
setTiemout(()=>{
resolve(1) //resolve中的值会传递到成功的回调函数参数中
},1000)
}).then((val)=>{ //onFulfiled
console.log(val)
},(e)=>{ //onRejected
console.log(e)
})
- Promise对象初始状态值为
pending
- 立即执行excutor,在excutor中可以通过resolve,reject方法改变promise状态,分别改为filfiled(成功)和rejected(失败)
- 状态一旦改变状态就凝固了,无法再变
- then方法中的回调函数会在状态改变后执行,成功调成功回调,失败调用失败回调
- resolve中的值会传递到成功的回调函数参数中 (失败类似)
2.2实现
思路:上述功能点1、2、3、5都比较好实现,4的话采用发布订阅模式也能实现
class Promise {
constructor(executor) {
this.status='pending' //三状态
this.value = undefined //参数
this.reason = undefined
this.onFulfilled = [] //发布订阅中储存回调
this.onRejected = []
let resolve = (value)=>{
if(this.status==='pending'){
this.status = 'fulfilled'
this.value = value
this.onFulfilled.forEach(fn=>fn(this.value)) //发布订阅模式,异步一改变状态则立即执行回调
}
}
let reject = (reason)=>{
if(this.status==='pending'){
this.status = 'rejected'
this.reason = reason
this.onRejected.forEach(fn=>fn(this.reason))
}
}
try{
executor(resolve,reject) //executor同步执行
}catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) { // 如果then的时候 根据当前状态执行成功或者失败
if(this.status==='fulfilled'){
onFulfilled(this.value)
}
if(this.status==='rejected'){
onRejected(this.reason)
}
if(this.status==='pending'){
this.onFulfilled.push(onFulfilled) //发布订阅模式储存异步回调
this.onRejected.push(onRejected)
}
}
}
3.添加链式调用
3.1 链式特性
1.如果promise中的then方法,无论是成功还是失败,他的返回结果是一个普通的时候就会把这个结果传递给外层的then的下一个then的成功回调
Promise.reject().then((val)=>{
return 'ok'
},()=>{
return 'err'
}).then((val)=>{
console.log('ok' + val)
},(e)=>{
console.log('err' + e)
})
// okerr 第一个then失败的回调返回的是普通值,还是走第二个的then中成功回调
2.如果成功或者失败的回调的返回值 返回是一个promise 那么会让这个promise执行 采用他的状态
Promise.resolve().then(()=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(1)
},1000)
})
}).then((val)=>{
console.log(val)
})
//一秒后打印1
3.2实现
这一版主要是实现链式调用,稍微绕一点,但是理清楚了也不难
首先明确一下,then后面会返回一个新的Promise,所以才能执行链式调用
第一个比较绕的地方,怎么让第二个then里面的回调执行?只要调用then返回的新promise(promise2)时的resolve方法就行了
第二个比较绕的地方就是参数是什么?我们看特性3.1,参数是什么要根据第一个then中回调的返回值来判断,返回值如果是正常值,如果是Piomise,,所以我们封装一个resolvePromise的方法来处理,参数的话有第一个then的回调,新创建的promise2,以及promise2里面的resolve.reject
需要改变的核心代码如下
let resolvePromise = (promise2, x, resolve, reject) => {...}
class Promise {
construcotr(){...}
then(){
let promise2 = new promise((resolve,reject)=>{
let x = onFulfiled() // onFulfilef是第一个then中的回调函数
resolvePromise(promise2,x, resolve, reject)
})
return promiese2
}
}
resolvePromise这个方法会判断onFulfiled返回值类型,如果是普通值会怎么样,如果是一个Promise会怎么样,如果报错会怎么样,详细实现方法可以参考promise A+规范 完整实现
let resolvePromise = (promise2, x, resolve, reject) => {
// 监测到环形链
if(promise2===x) return new TypeError('chaining cycle detected for promise')
if(typeof x ==='function' ||(typeof x ==='object' && x!==null)){
try{
//尝试取出then,有问题报错
let then = x.then
if(typeof then === 'function'){ //这里是最绕的,想清楚promise2和x的关系,x.then会不会执行取决于使用者的逻辑,会不会在第一个then中回调函数中返回的promise中调用它的resolve改变状态
then.call(x,resolve,reject)
}else{// then不是function
resolve(x)
}
}catch (e) {
reject(e)
}
}else{ //普通类型
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallback = []
this.onRejectedCallback = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.onFulfilledCallback.forEach(fn => fn(this.value))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.onRejectedCallback.forEach(fn => fn(this.reason))
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) { // 如果then的时候 根据当前状态执行成功或者失败
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => { //这里之所以异步是因为必须保证resolvePromise(promise2, x, resolve, reject)时Promise2创建完成
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === 'pending') {
this.onFulfilledCallback.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallback.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
基本面试5-10分钟代码写到这里,都能给满分通过,剩下的就是4个打补丁的地方了
4.打补丁
4.1 补丁点
实际上是A+规范测试用例的补丁,我按重要程度往下排,前面的必须做到能写出来(面试可以不写),后面的知道即可
- then的默认参数配置
- x可能是个Promise,它的返回值还可能是个Pormise,这个Promised的返回值还可能是个Promise.....
- 调用Promise的resolve方法,如果参数是个promise怎么办 (这个不在A+规范里,但是新版promise实现了)
- 别人实现的可能不规范,我们的resolvePromise需要加一点限制,改变了状态就不能再变了 (这个在A+规范测试用例里,但是我感觉意义不大)
4.1.1 默认参数
Promise.resolve(1).then().then().then().then((val)=>{
console.log(val) //1
})
//失败也是类似的传递
可以默认传递一个回调函数
then(onFufilled,onRejected){
onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
...
}
4.1.2 x中promise嵌套
这个也不难,递归调用resolvePromise去解析
let resolvePromise = (promise2,x,resolve,reject) => {
...
then = x.then
/*这个是之前的核心代码 then.call(x,resolve,reject)
*实际等同于 then.call(x,(y)=>{
* resolve(y) 这个y是x作为promise的返回值,现在这个y可能是个promise所以再递归调用resolvePromise去解析
* },reject)
*/
改成这样:
then.call(x,(y)=>{
resolvePromise((promise2,y,resolve,reject)
},reject)
...
}
4.1.3 resolve中是promise
constructor(executor){
...
let resolve = (value) =>{ // 如果resolve的值时一个promise
if(value instanceof Promise){
// 我就让这个promise执行,把成功的结果再次判断
return value.then(resolve,reject) //参数会自动传递到resolve里面去
}
}
...
5.添加方法
Promise比较重要的方法一共有五个方法
5.1 Promise.resovle
把一个对象包装成Promise对象,特别注意状态不一定是成功的 各种注意事项请看阮一峰es6-promise 直接记忆不好记忆,但是结合源码很简单,理所当然
static resolve(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
5.2 Promise.reject
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
static reject(err){
return new Promise((resolve,reject)=>{
reject(err);
})
}
5.3 PromiseInstance.prototype.finally
这个是实例方法,其他几个都是类方法
无论成功还是失败都会调用,所以可定返回的也是一个Promimse,成功失败都会调用传入的回调,
finally不接受值,返回的Promise的状态受前一个promise状态的影响
finally如果在中间同时回调返回一个promise则会等待promise
Promise.resolve(1).finally(
(a)=>{
return new Promise((resolve)=>{
setTimeout(function () {
resolve(2)
},3000)
})
}
).then((data)=>{
console.log(data)
})
等待3秒后打印1
finally实现
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
5.4 Promise.race Promise.all
race和all一个是谁先调用谁执行后面then中的回调,一个是全部调用才执行后面then中的回调 他们都需要对参数中传入的数组进行遍历
all的实现需要借助计数器,这也是实现异步任务通知的一种方法
直接完成或者异步完成都会使计数器加1 当计数器和数组长度相等时就是all方法完成的时候
,然后把结果数组传到下一个回调
race的实现就是,遍历数组中元素current,都去改变返回promise的值,谁先改变就取谁的值传到会带到函数里面
return promose((resolve,reject)=>{
if(isPromise(current)){
current.then(resolve,reject)
}else{
resolve(current)
}
})
具体实现见6
6.完整实现
let resolvePromise = (promise2,x,resolve,reject) => {
if(promise2 === x){
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 如果调用了失败 就不能再调用成功 调用成功也不能再调用失败
let called;
if(typeof x ==='function' || (typeof x === 'object' && x!== null) ){
try{
let then = x.then; // Object,dedefineProperty
if(typeof then === 'function'){
then.call(x,(y)=>{ // x.then(y=>,err=>)
if(called) return;
called = true
// y有可能解析出来的还是一个promise
// 在去调用resolvePromise方法 递归解析的过程
// resolve(y)
resolvePromise(promise2,y,resolve,reject); // 总有y是普通值的时候
},e=>{
if(called) return;
called = true
reject(e);
})
}else{
if(called) return;
called = true
resolve(x);
}
}catch(e){
if(called) return;
called = true
reject(e);
}
}else{
if(called) return;
called = true
resolve(x); // '123' 123
}
}
class Promise{
constructor(executor){
this.value = undefined;
this.reason = undefined;
this.status = 'pending';
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) =>{ // 如果resolve的值时一个promise
// if(typeof value === 'function' || (typeof value == 'object'&&value !== null)){
// if(typeof value.then == 'function'){
// return value.then(resolve,reject)
// }
// }
if(value instanceof Promise){
// 我就让这个promise执行,把成功的结果再次判断
return value.then(resolve,reject) //参数会自动传递到resolve里面去
}
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(e){
console.log(e)
reject(e);
}
}
then(onFufilled,onRejected){
// 可选参数的配置
onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}
let promise2 = new Promise((resolve,reject)=>{
if(this.status === 'fulfilled'){
setTimeout(()=>{ // 为了保证promise2 已经产生了
try{
let x = onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
console.log(e);
reject(e);
}
})
}
if(this.status === 'rejected'){
setTimeout(() => {
try{
let x= onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
}
if(this.status === 'pending'){
this.onResolvedCallbacks.push(()=>{
setTimeout(() => {
try{
let x = onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
this.onRejectedCallbacks.push(()=>{
setTimeout(() => {
try{
let x= onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
})
return promise2
}
finally(callback){
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
}
catch(errCallback){ // catch是then的一个别名而已
return this.then(null,errCallback)
}
static resolve(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
static reject(err){
return new Promise((resolve,reject)=>{
reject(err);
})
}
static race(values){
return new Promise((resolve,reject)=>{
for(let i = 0 ; i<values.length;i++){
let current = values[i];
if(isPromise(current)){
current.then(resolve,reject)
}else{
resolve(current)
}
}
})
}
static all(values){
return new Promise((resolve,reject)=>{
let arr = []; // 最终的结果
let i = 0;
function processData(key,val) {
arr[key] = val;
if(++i == values.length){
resolve(arr);
}
}
for(let i = 0 ; i<values.length;i++){
let current = values[i];
if(isPromise(current)){
current.then(y=>{
processData(i,y);
},reject)
}else{
processData(i,current);
}
}
})
}
}
Promise.deferred = () => { // 测试方法
let dfd = {};
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd; // 可以检测这个对象上的promise属性 resolve方法 reject方法
}
module.exports = Promise;
// 全局安装 只能在命令中使用 sudo npm install promises-aplus-tests -g
// promises-aplus-tests promise.js
// 本地安装 可以在命令下 和 我们的代码中使用
7.面试题
7.1 请写出下面代码运行结果
Promise.reject(1).then().finally(
(a)=>{
console.log('a:'a) //undefined
setTimeout(function () {
console.log(2)
},3000)
}
).then((data)=>{
console.log(3)
console.log(data)
},(e)=>{
console.log('error'+e) //打印error1
})
//
答案:a:undefined error1 过两秒 2
7.2 promise构造器是同步还是异步,then方法呢
来源:微医 答案:同步,异步 源码里面写的很清楚
7.3 模拟实现一个Promise.finally
答案:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
7.4 介绍一下Promose.all的使用,原理及错误处理
使用:需要同时获取多个东西后再执行回调 原理:返回一个Promise: p 遍历参数数组,若不是promise,直接加入到结果数组arr中 计数器++ 如果是Promise,等Promise执行完再讲结果加到加过数组 计数器++ 计数器===数组长度时证明全部完成,p.resolve(结果数组arr) 错误处理: p.reject(e)
7.5 设计并实现Promise.race
答案:
Promise._race = promises => new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject)
})
})
8 总结
总结了Promise的实现,以及面试常见考点,相信如果全部理解了,面试再问promise肯定可以加分不少。由于技术有限,如果阅读中发现有什么错误,请在留言指出。
小编开了个逐点突破系列,一篇文章来讲一个知识点,学习要系统,知识点也需要归纳总结,文章还会包括常见的相关面试题,所以这个系列还请大家多多支持啦!
9最后
文章中出现的面试题还没有看过瘾的可以【点击这里】免费获取完整版前端面试题解析PDF哦!
如果你觉得本文对你有很大的帮助,喜欢这个系列,请评论点赞转发来告诉我哦!你们的支持是我最大动力!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!