ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

es6 promise知识点 王者段位前来挑战

2022-01-28 19:07:11  阅读:192  来源: 互联网

标签:知识点 resolve console log es6 段位 promise reject Promise


关于promise的知识点,暂且分为 青铜、白银、黄金、铂金、钻石、星耀、王者这几个段位。可能是王者玩多了吧,你到哪一个段位了?
将关于promise的

青铜

你已经用过promise解决过异步编程了,并知道通过调用.then()和.catch()来处理回调函数。

基本用法

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
  	// 异步操作失败
    reject(error);
  }
});
promise.then((value) => {
	// resolve 传递过来的value
	// 做一些成功后的回调处理
}).catch((error) => {
	// error 为 reject(error) 传递过来的;
	// 做一些异步操作失败后的回调处理
})

白银

你应该知道promise的两个特点和三个缺点。

两个特点

  • promise对象有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败),三种对象的状态不受外界干扰
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

三个缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise.prototype.finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
学会使用finally()这一点至关重要。
在前端日常使用中,当我们通过promise请求一个接口的数据,当请求开始时,我们会让页面处于loading状态,但接口请求结束后(无论成功或者失败)就应该结束页面的loading状态。

// 接口调用前让页面处于loading状态
commit('setLoading', true);
let checkResult = checkWarranty({SerialNumber: data.SN, CountryCode:config.user.Country})
.then((result: any) => {
    return result.data;
}).finally(() => {
	// 接口调用后取消页面的loading状态
    commit('setLoading', false);
});

我改过许多bug,都是因为没将接口调用结束后的处理放在finally的回调中,而是在then中进行处理,没有考虑过接口请求失败的情况。

黄金

promise.all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p1 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value1');
	  } else {
	    reject('error1');
	  }
});
const p2 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value2');
	  } else {
	    reject('error2');
	  }
});
const p3 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value3');
	  } else {
	    reject('error3');
	  }
});
const p = Promise.all([p1, p2, p3]);

另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。

  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
p.then((result) => {
	console.log(result) // [value1,value2,value3]
})
  1. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
p.catch((error) => {
	console.log(error) // error1或者error2或者error3
})

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) => {
	reject('error1');
}).catch((error) => {
    console.log('p1',error)
});

const p2 = new Promise((resolve, reject) => {
	resolve('value2')
});

Promise.all([p1,p2]).catch((error) => {
    console.log('promise.all', error )
})

// p1 error1

如果p1没有自己的catch方法,就会执行promise.all的catch方法

const p1 = new Promise((resolve, reject) => {
	reject('error1');
})
const p2 = new Promise((resolve, reject) => {
	resolve('value2')
});

Promise.all([p1,p2]).catch((error) => {
    console.log('promise.all', error )
})
// promise.all error1

promise.race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p1 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value1');
	  } else {
	    reject('error1');
	  }
});
const p2 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value2');
	  } else {
	    reject('error2');
	  }
});
const p3 = new Promise((resolve, reject) => {
	if (/* 异步操作成功 */){
	    resolve('value3');
	  } else {
	    reject('error3');
	  }
});
const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    resolve('value2')
});

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})
// promise.race value2
const p1 = new Promise((resolve, reject) => {

    reject('error1');
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value2')
    }, 1000);
});

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})
// promise.race error1

如果promise实例使用了catch或者then进行拦截,那么在promise.race对应的结果中,将无法获取到值

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    resolve('value2')
}).then((val) => {
    console.log(val)
})

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})

/** value2
promise.race undefined
*/

认识promise应该与EventLoop结合

promise本身是处理异步的,但代码执行的先后顺序你真的了解吗?

console.log('promise 外部1')
const p1 = new Promise((resolve, reject) => {
    console.log('promise 内部1')
    resolve()
}).then(() => {
    console.log('promise then1')
}).finally(() => {
    console.log('promise finally1')
})

console.log('promise 外部2')

const p2 = new Promise((resolve, reject) => {
    console.log('promise 内部2')
    reject()
}).catch(() => {
    console.log('promise catch2')
}).finally(() => {
    console.log('promise finally2')
});

console.log('promise 外部3')
const p3 = new Promise((resolve, reject) => {
    console.log('promise 内部3')
    reject()
}).catch(() => {
    console.log('promise catch3')
}).finally(() => {
    console.log('promise finally3')
});
console.log('promise 外部4')

const p4 = new Promise((resolve, reject) => {
    console.log('promise 内部4')
    resolve()
}).then(() => {
    console.log('promise then4')
}).finally(() => {
    console.log('promise finally4')
});

你觉得上面的代码先后执行顺序是怎么样的?

/**
promise 外部1
promise 内部1
promise 外部2
promise 内部2
promise 外部3
promise 内部3
promise 外部4
promise 内部4
promise then1
promise catch2
promise catch3
promise then4
promise finally1
promise finally2
promise finally3
promise finally4
*/

由上例可知

  • promise的then catch finally 在eventLoop中都属于微任务,其它代码则是按从上至下同步执行。
  • 同样是微任务,先被链式调用的先执行,如finally总是被调用在then或者catch之后,所以所有的finally属于第二批微任务,后执行。第一批微任务分别是promise then1,promise catch2,promise catch3,promise then4
  • 同一批微任务的执行顺序自然而然按照从上至下的顺序进行。

铂金

也许你经常使用promise,之前也看过es6的promise规范,但最新的两个promise方法你有关注吗?搞技术的,总是需要不断学习,相信你们都是卷王!

Promise.allSettled

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。

该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

const p1 = new Promise((resolve, reject) => {
    resolve('value1')
})

const p2 = new Promise((resolve, reject) => {
    reject('error2')
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

const p4 = new Promise((resolve, reject) => {
    resolve('value4')
})

const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => {
    console.log(result)
})

/** 
[
  { status: 'fulfilled', value: 'value1' },
  { status: 'rejected', reason: 'error2' },
  { status: 'rejected', reason: 'error3' },
  { status: 'fulfilled', value: 'value4' }
]
*/

如果参数promise实例有调用then或者catch,Promise.allSettled对应的value或者reason可能会是undefined

const p1 = new Promise((resolve, reject) => {
    resolve('value1')
})

const p2 = new Promise((resolve, reject) => {
    reject('error2')
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
}).catch((error) => {
    console.log(error)
})

const p4 = new Promise((resolve, reject) => {
    resolve('value4')
}).then((value) => {
    console.log(value)
})

const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => {
    console.log(result)
})

/** 
error3
value4
[
  { status: 'fulfilled', value: 'value1' },
  { status: 'rejected', reason: 'error2' },
  { status: 'fulfilled', value: undefined },
  { status: 'fulfilled', value: undefined }
]
*/

Promise.any

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;并且返回最先resolve执行完成的promise实例

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    resolve('value3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})

// promise.any value3

如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})

// promise.any AggregateError: All promises were rejected

如果promise对resolve使用了then拦截,promise.any也只能拿到undefined,但还是fulfilled状态

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value1');
    }, 1000);
}).then((res) => {
    console.log(res)
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})
// promise.any undefined

promise和async

async和promise就是处理异步函数的,但async返回的就是一个promise对象

如果要处理成功和捕获错误,可以这样写。

(async () => f())()
.then(...)
.catch(...)

钻石

promise.try

在日常使用promise的过程中,光依靠promise.catch来捕获异常是不够,因为catch只能捕获promise内部的错误。

function promiseTest() {
    console.log(dx) // 同步错误
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //异步错误
        }, 1000);
    })
}

promiseTest().then((res) => {
    console.log(res)
}).catch((err) => {
    console.log('p1',err)
})
// ReferenceError: dx is not defined

我们调用一个方法,方法返回的可能是一个promise,但该方法会执行,可能会产生一些同步的错误写在promise外部,为此不得不使用try catch的方式。

function promiseTest() {
    console.log(dx) // 同步错误
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //异步错误
        }, 1000);
    })
}

try {
    promiseTest().then((res) => {
        console.log(res)
    }).catch((err) => {
        console.log('p1', err)
    })
} catch (e) {
    console.log('同步',e)
}
// 同步 ReferenceError: dx is not defined

为了一并解决同步和异步错误需要分开捕获的问题,使用promise.try

var Promise = require('bluebird');
function promiseTest() {
    console.log(dx) // 同步错误
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //异步错误
        }, 1000);
    })
}

Promise.try(() => {
    return promiseTest()
}).catch((e) => {
    console.log(e)
})

注意 promise.try到现在还只是一个提案(很久以前就提了,没有下文,需要使用Promise 库Bluebird、Q等,或引入Polyfill才可以)

手写一个极简版的promise

// 三个状态:PENDING、FULFILLED、REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    // 默认状态为 PENDING
    this.status = PENDING;
    // 存放成功状态的值,默认为 undefined
    this.value = undefined;
    // 存放失败状态的值,默认为 undefined
    this.reason = undefined;

    // 调用此方法就是成功
    let resolve = (value) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    } 

    // 调用此方法就是失败
    let reject = (reason) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    try {
      // 立即执行,将 resolve 和 reject 函数传给使用者  
      executor(resolve,reject)
    } catch (error) {
      // 发生异常时执行失败逻辑
      reject(error)
    }
  }

  // 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

星耀

手写一个能链式调用的promise

一个简易版本的promise需要做到哪些呢

  1. promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」
  2. new promise时, 需要传递一个executor()执行器,执行器立即执行;
  3. executor接受两个参数,分别是resolve和reject;
  4. promise 的默认状态是 pending;
  5. promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
  6. promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」
  7. promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;
  8. promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
  9. 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;
  10. 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;
  11. 实现链式调用then在什么状态下返回then或者返回catch
// new promise时, 需要传递一个executor()执行器,执行器立即执行;
function MyPromise(executor) {
    // promise 有三个状态:pending,fulfilled,or rejected;promise 的默认状态是 pending;
    this.state = 'pending';
    // promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;
    this.value;
    // promise 有一个reason保存失败状态的值;
    this.reason;

    this.resolve = (result) => {
        // Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。
        if (this.state === 'pending') {
            this.state = 'fulfilled';
            this.value = result
        }
    }

    this.reject = (error) => {
        // Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。
        if (this.state === 'pending') {
            this.state = 'rejected';
            this.reason = error
        }
    }

    // promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;
    this.then = (onFulfilled, onRejected) => {
        try {
            // 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;
            if (this.state === 'fulfilled') {
                let result = onFulfilled(this.value)
                // 当then已经被调用过,value值恢复为undefined,下一次then如果没有新的promise实例(this还是这个this),那么得到的值就是undefined,
                this.value = undefined
                // 如果result的结果也是一个MyPromise实例
                if (result instanceof MyPromise) {
                    return result
                }

            } else if (this.state === 'rejected') {
                let result = onRejected(this.reason)
                // 如果result的结果也是一个MyPromise实例
                if (result instanceof MyPromise) {
                    return result
                }
            }

            return {
                myCatch: this.myCatch,
                then: this.then,
                finally: this.finally
            }
        } catch {
            // 如果state是rejected,又没有传递onRejected,则会报错
            return {
                myCatch: this.myCatch,
                then: this.then,
                finally: this.finally
            }
        }
    }
    // promise 必须有一个catch方法,catch当rejected执行函数的参数
    this.myCatch = (onRejected) => {
        if (this.state === 'rejected') {
            let result = onRejected(this.reason)
            this.reason = undefined
            // 如果result的结果也是一个MyPromise实例
            if (result instanceof MyPromise) {
                return result
            }
        }

        return {
            myCatch: this.myCatch,
            then: this.then,
            finally: this.finally
        }
    }

    this.finally = (callback) => {
        if (this.state !== 'pending') {
            let result = callback()
            // 如果result的结果也是一个MyPromise实例
            if (result instanceof MyPromise) {
                return result
            }
        }
        // catch在state为fulfilled时返回then
        return {
            myCatch: this.myCatch,
            then: this.then,
            finally: this.finally
        }
    }

    try {
        executor(this.resolve, this.reject)
    } catch (err) {
        this.reject(err)
    }
}

让我们来分别测试一下 reslove 和 reject以及都不调用这三种情况

new MyPromise((resolve, reject) => {
    reject('val1')
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('出错了', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('出错了', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// 出错了 val1
// finally
// finally
// 1 2222
new MyPromise((resolve, reject) => {
    resolve('val1')
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('出错了', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('出错了', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// val1
// undefined
// finally
// finally
// 1 undefined
new MyPromise((resolve, reject) => {
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('出错了', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('出错了', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// 

王者

上面的内容只是模拟了promise比较简单的一些功能,没有处理promise的异步,实现链式调用的方式也不优雅,另外还有promise的一些其它api并未封装,我觉得我只能到星耀段位了。
来看看真正的王者,教你手写promise
https://zhuanlan.zhihu.com/p/183801144

最强王者

在我心中,技术永远没有最高段位,对未来抱有更高的期待。
但还是制定一个标准 属于那些能够提供 es6 promise新api提案或者想法,并被官方采纳的人

标签:知识点,resolve,console,log,es6,段位,promise,reject,Promise
来源: https://blog.csdn.net/glorydx/article/details/122497871

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有