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

Posted 程序员超时空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了es6 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]
    )

  2. 只要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本身是处理异步的,但代码执行的先后顺序你真的了解吗?
链式调用中,只有前一个 then、catch、finally 的回调执行完毕后,跟着的 回调才会被加入至微任务队列。

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代码执行顺序

同一个 Promise 的每个链式调用的开端会首先依次进入微任务队列。

let p = Promise.resolve();

p.then(() => 
  console.log("then1");
  Promise.resolve().then(() => 
    console.log("then1-1");
  );
).then(() => 
  console.log("then1-2");
);

p.then(() => 
  console.log("then2");
);

实际结果是 then1-1 then1-2 then2
then 每次都会返回一个新的 Promise,此时的 p 已经不是 Promise.resolve() 生成的,而是 then1-2 生成的,因此 then2 应该是在 then1-2后执行

钻石

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.resolve()
  .then(() => 
    console.log("then1");
    Promise.resolve()
      .then(() => 
        console.log("then1-1");
        return Promise.resolve();
      )
      .then(() => 
        console.log("then1-2");
      );
  )
  .then(() => 
    console.log("then2");
  )
  .then(() => 
    console.log("then3");
  )
  .then(() => 
    console.log("then4");
  );

then1-2会何时被打印?
then1
then1-1
then2
then3
then4
then1-2

根据规范 25.6.1.3.2[3],当 Promise resolve return了一个 Promise 时,会产生一个 NewPromiseResolveThenableJob,这是属于 Promise Jobs 中的一种,也就是微任务。

This Job uses the supplied thenable and its then method to resolve the
given promise. This process must take place as a Job to ensure that
the evaluation of the then method occurs after evaluation of any
surrounding code has completed.

并且该 Jobs 还会调用一次 then 函数来 resolve Promise,这也就又生成了一次微任务。

换句话说,当在then或者catch或者finally中return 一个promise并且reslove或者reject。这一过程会插入两次微任务。
then1是第一次微任务
then1-1和then2是第二次微任务
本来then1-2和then3是第三次微任务,但由于return promise.reslove插入了两次微任务,导致then1-2实际已经变成了第五次微任务,所以then3是第三次微任务
then4是第四次微任务
then1-2是第五次微任务。

王者

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

最强王者

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

以上是关于es6 promise知识点 王者段位前来挑战的主要内容,如果未能解决你的问题,请参考以下文章

Unity实战之王者荣耀段位计算

你们的王者荣耀现在什么段位呢?如何快速提升段位?

青铜到王者,快速提升你 MySQL 数据库的段位!

如果在加密领域有段位,你是“青铜”还是“王者”?

春季班|C++信奥班(LEVEL2),还有更高的“段位”等你挑战!

并发王者课-青铜1:牛刀小试-如何创建线程之初体验