Promise系列

306 阅读41分钟

为什么要用promise:JS执行时,一次只能执行一个任务,会阻塞其他任务,由此导致js的所有网络操作,浏览器事件都必须是异步,异步执行执行可用回调函数执行,但因回调嵌套使用外部回调函数异步执行的结果是嵌套的回调执行的条件,所以回调过度易出现回调地狱,导致代码臃肿,可读性和可维护性差,易产生bug,且只能在回调中处理异常,为解决这些问题才有了promise

promise写法更直观更灵活,支持链式调用,用在异步回调中,返回的是一个对象,且可以在外层捕获异步函数的异常信息

  • 有3个状态:状态的改变只有2中情况,且一经改变不可更改,是不可逆的

    • pending:进行中,表示promise还在执行阶段
    • fulilled:成功状态,表示p执行完成
    • rejected:失败阶段,
  • 2个过程: pending -> fulilled、pending -> rejected

  • promise函数对象上的方法(包括构造函数中的方法及原型上的方法):promise.XXX

    • resolve():把p状态从pending改为完成,并将异步结果或错误结果当参数返回
    • reject():pending -> rejected
    • all():包含n个p的数组,返回一个新的p,只有所有的p都成功才成功并返回所有结果,有一个失败就直接失败失败原因是第一个失败的结果
    • race():包含n个p的数组,第一个完成的p的结果状态(成功或失败)就是最终结果状态
  • promise原型对象上的方法(一般用于对象实例共享,这样就不用每次都初始化一个个实例使为其分配相应内存了) promise.prototype.XXX

    • then(onFulifilled,onRejected):分别在p状态变为成功或失败后执行
    • catch(onRejected):在p状态变为rejected后执行
    • finally():无论成功或失败都可做一些清理工作,如关闭加载动画
  • then是如何实现链式调用的?

创建并返回了新的promise实例

它的状态变成fulfilled的节点实在上一个p的回调执行完毕时,即当一个p的状态成功后会执行其回调,而回调返回的结果会被当作value返回给下一个p,同时下一个p的状态也会被改变(执行resolve或reject),然后再去执行其回调,以此类推链式调用的效应就出来了

  • 如何中断promise?

有且只有一个方法,返回pending状态

因为当状态从pending变为fulfilled或rejected时就会调用p的then方法,若一直在pending状态的话,就不会执行到then()了

这个原理利用了promise.then()返回新的p的结果状态是有then()指定的回调函执行的结果决定,所以在promise.then()返回一个pending状态的对象,promise.then()返回的新的p的结果状态就一直是pending,后面的then也不会执行

  • promise缺点:无法取消,当处于pending时,无法得知目前进展到哪个阶段了,

all

若其中之一失败,怎么能拿到其他成功的结果:
let p1 = new Promise((resolve,reject)=> {
   setTimeout(()=> {
           resolve('f11111')
       },2000)
})
let p2 = new Promise((resolve,reject)=> {
       setTimeout(()=> {
           resolve('f22222')
       },1000)
})
let p3 = new Promise((resolve,reject)=> {
       setTimeout(()=> {
           reject('err-f33333')
       },3000)
 })
 
 const arr = [p1, p2, p3]

Promise.all(a).then(data=> { 
console.log(data) 
}).catch(err=> { console.log('糟糕出错了') })
//若错误方法P3没有自己的catch方法,就会调用Promise.all()的catch方法、该实例的最终结果被Promise.all的catch方法捕获,成功状态的就不会输出

//若p2自己定义了catch方法,那它一旦被rejected,并不会触发Promise.all()的catch方法,那其他成功状态的也会被输出

tips:使用catch捕获错误,然后就能再执行catch后面的正确函数,不使用catch捕获错误,一旦出错,就会停止继续执行正确函数

//最输出[p1,p2,p3]
使用场景

多个请求结果合并在一起

具体描述:一个页面,有多个请求,我们需求所有的请求都返回数据后再一起处理渲染

控制并发数all

有待请求接口20个,限制每次只能发3个,即同一时刻最多有3个正在发送请求,每当3个中有一个请求完成,则从待请求中再取出一个发出,保证当前兵法仍有3个直到完成所有

继发请求(一个请求结束后再请求后一个),那么应该是按顺序打印,理论上所有请求的总时间等于每个请求所花时间之和

并发请求(假设请求数不会太多,不超过限制),顺序是按时间从小到大打印,理论上所有请求的总时间等于最长的那个时间

// 模仿一个fetch的异步函数,返回promise
  // function mockFetch(param) {
  //   return new Promise((resovle) => {
  //     setTimeout(() => {
  //       resovle(param);
  //     }, 2000);
  //   });
  // }

  function limitedRequest(urls, limit) {
    const pool = [];
    // 处理limit比urls.length 还要大的情况,若输入的数字比limit要打,则以limit为主
    const initSize = Math.min(urls.length, limit);
    for (let i = 0; i < initSize; i++) {
      // 一次性放入初始的个数
      pool.push(run(urls.splice(0, 1)));
    }

    // r 代表promise完成的resolve回调函数
    // r 函数无论什么样的结果都返回promise,来确保最终promise.all可以正确执行
    
    function r() {
      console.log('当前并发度:', pool.length);
      if (urls.length === 0) {
        console.log('并发请求已经全部发起');
        return Promise.resolve();
      }
      return run(urls.splice(0, 1));
    }

    // 调用一次请求
    function run(url) {
      // return mockFetch(url).then(r);
      const fetch = new Promise((resolve) => {
        setTimeout(() => {
          resolve(url)
        }, 1000)
      })
      return fetch.then(res)
    }
    // 全部请求完成的回调
    Promise.all(pool).then(() => {
      console.log('请求已经全部结束');
    });
  }

  // 函数调用
  // limitedRequest([1, 2, 3, 4, 5, 6, 7, 8], 3);
  const p1 = new Promise((resolve,reject)=>{resolve('p1')})
  const p2 = Promise.resolve('2')
  const p3 = new Promise((resolve,reject)=>{setTimeout(resolve,100,'foo')})
  const p4 = 20
  const arr = [p1,p2,p3,p4]
  limitedRequest(arr, 3);

promise原理

juejin.cn/post/688312…

手写限制并发数1

//首先,我们分析这个题目的入参出参,入参已经确定,出参肯定返回一个新的Promise对象,并且这个对象一定是resolve的;所以可以先把函数整体框架搭好;
    //function concurrentPromise(promises,max){return new Promise(reslove=>{// 详细代码})}
    //其次,我们考虑一些异常场景。当传入的第一个参数不是数组,或者是数组但不是promise数组时是我们针对promise的一些处理是会有异常的,所以针对入参我们需要做一些校验;
    //再次,对于promise对象,我们肯定是一个一个单独处理,处理完一个才处理下一个,所以我们需要一个函数来处理单个的promise对象,然后对于当前是处理的哪个promise对象也需要做好标记,因此,我们可以先写出如下代码:
    //最后,我们只需要考虑一开始需要调用几次handlePromise方法,然后一开始的时候调用几次就好;因为考虑到max > promises.length的场景,只需要调用promise.length次即可,代码实现如下:
    
  function concurrentPromise(promises, max) {
        // 如果传入的不是一个数组,抛出异常
        if (!Array.isArray(promises)) {
            throw new Error("请传入promise数组");
        }
        // 如果传入数组中不全是Promise对象,抛出异常
        const isAllPromise = promises.every((v) => {
            return v instanceof Promise;
        });
        if (!isAllPromise) {
            throw new Error("请传入promise数组");
        }
        if (typeof max !== "number") {
            throw new Error("请传入正确的最大并发数");
        }
        // 返回一个Promise对象
        return new Promise((resolve) => {
            const len = promises.length;
            if (len === 0) {
                resolve([]);
            }
            // 存放所有promise对象结束pending状态后的结果
            const result = [];
            // 当前处理的promise对象的下标
            let index = 0;
            // 已经处理的promise对象的个数
            let count = 0;
            // 处理单个promise的方法
            async function handlePromise() {
                // 如果所有的promise对象都已经取出处理过了,那么就直接返回
                if (index >= len) {
                    return;
                }
                // 取出将要处理的promise对象
                const promise = promises[index];
                // 存储当前处理的promise对象在数组中的索引,放入result数组中时需要对应上
                const i = index;
                // 处理完这个promise对象后,下标需要加1,下一次就调用函数时需要取出下一个promise对象去处理
                index++;
                try {
                    const res = await promise.then();
                    console.log(res)
                    // 拿到结果之后将结果存入result数组中,下标要对应上
                    result[i] = res;
                } catch (error) {
                    // 就算promise对象reject了,也需要把reject原因存入result数组中
                    result[i] = error;
                } finally {
                    // 每处理完一个promise对象,count都需要自增1
                    count++;
                    // 如果所有的promise对象都处理完了,那么把结果resolve出去即可
                    if (count === len) {
                        resolve(result);
                    } else {
                        // 处理完这个promise对象时,调用函数处理下一个promise对象
                        handlePromise();
                    }
                }
            }
            // 初始化调用handlePromise函数的次数,有可能max比数组长度大
            const times = Math.min(len, max);
            for (let i = 0; i < times; i++) {
                handlePromise();
            }
        });
    }

手写限制并发2

    function limitRequest(urls, limit) {
        const pool = []
        const len = urls.length
        if (!Array.isArray(urls)) throw new Error("请传入promise数组"); //传入的不是一个数组,抛出异常

        if (limit > len) throw new Error("请传入正确的最大并发数");

        const initSize = Math.min(len, limit)
        for (let i = 0; i < initSize; i++) {
            pool.push(run(urls.splice(0, 1)));
        }

        function res() {
            console.log('当前并发度', pool.length)

            if (urls.length === 0) {
                console.log('并发请求已经全部发起')
                return Promise.resolve()
            }
            return run(urls.splice(0, 1))
        }

        function run(url) {
            const fetch = new Promise((resolve) => {
                setTimeout(() => {
                    resolve(url)
                    console.log(url, '哈哈哈')

                }, 2000)
            })
            return fetch.then(res)

        }

        Promise.all(pool).then(() => {
            console.log('请求已经全部结束')
        })

    }

    let p1 = new Promise((resolve, reject) => {
        resolve('p1')
    })
    let p2 = Promise.resolve('2')
    let p3 = Promise.reject('3')
    let p4 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'foo')
    })
    let p5 = 23


    const array = [p1, p2, p3, p4, p5]
    limitRequest(array, 3)

手写promise

   // 一个promise类里面都应该包含哪些内容了:
    // promise状态:pending,fulfilled,rejected
    // promise返回值
    // 执行器:promise执行入口(也就是你传入的那个函数)
    // resolve:改变promise状态为fulfilled
    // reject:改变promise状态为rejected
    // then:接收两个回调,onFulfilled, onRejected。分别在promise状态变为fulfiled或rejected后执行
    // catch:接受一个回调,在promise状态变为rejected后执行


    class myPromise {
        static PENDING = 'pending'
        static FULFILLEd = 'fulfilled'
        static REJECTED = 'rejected'
        constructor(
            init) { //Promise的执行器,它其实是我们在new Promise时传入的一个回调函数,这个函数本身是同步的,也就是说在new Promise时它就会执行,这也是我们操作promise的入口。
            //p构造函数,p在初始化之前传入的函数是同步执行的
            //init执行器函数会在p内部立即调用,异步操作在此执行
            this.state = myPromise.PENDING // promise状态
            // this.promiseRes = null // promise返回值

            // 将成功、失败的结果放在this上,便于then、catch访问
            this.value = null;
            this.reason = null;


            //执行到then时因传入的立即执行函数未执行resolved或reject,所以p的状态还是pending,这时要把then里的回掉函数保存起来
            this.resolveCallback = [] //成功回调函数队列
            this.rejectCallback = [] //失败回调函数队列

            //resolve与reject方法,这两个函数作为参数传到执行器函数中,用以后续改变Promise状态

            //执行resolve时,此时p的callbacks里的回调被执行,并将当前p状态改为resolved,此函数结果也会被保存到当前对象中,所以resolved的功能是执行callback里的函数,并保存在value或者reason,并改为p的状态
            const resolve = value => {
                //作用是,将Promise对象的状态从“未完成”变成“成功”(即从pending变为resolve),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
                if (this.state !== myPromise.PENDING) return //p的状态只能改一次,所以当前装不是pending就不能执行
                // 只有当状态为pending时才改变,保证状态一旦改变就不会再变
                if (this.state === myPromise.PENDING) {
                    this.state = myPromise.FULFILLEd //改变状态
                    this.value = value; //返回值
                    //依次调用成功回调
                    this.resolveCallback.forEach(fn => fn(this.value))
                }
            }
            const reject = reason => {} //道理同resolve、参数使用REJECTED、reason


            // 生成实例后立即调用init、把内部的resolve和reject传入init,用户可调用resolve和reject

            try {
                init(resolve, reject) //init就是初始化执行器
            } catch (err) {
                reject(err) //init执行器函数出错时,用以让promise状态变为rejected
            }
        }
        then(onFulfilled, onRejected) {
            // 实现值穿透 当then中传入的不是函数,则这个p返回上一个p的值,且每一个then返回的p的状态都为resolved
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
            onReject = typeof onReject === 'function' ? onReject : reason => {
                throw new Error(reason)
            }

            const {
                state,
                value
            } = this
            //     // 保存前一个promise的this
            const self = this;

            let promise = new myPromise((reso, reje) => { //返回新的p,而新的p状态则有当前then执行结果来确定
                let fulfilled = () => {
                    try {
                        const result = onFulfilled(self.value); // 承前
                        return result instanceof MyPromise ? result.then(resolve, reject) : resolve(
                            result); //启后


                        //若回掉返回的是p,return的p的结果就是这个p的结果,若p执行了resolve返回的新的p的状态则是resolve,否则reject
                        //若不是p,return的新的P的状态改为resolved,value就是返回值,回掉返回了value,就把value传入resolve函数,将当前新的p的状态改为resolved,同时将value保存到当前新的p的结果里
                    } catch (err) {
                        reject(err)
                    }
                }


                let rejected = () => {
                    try {
                        const result = onReject(self.reason);
                        return result instanceof MyPromise ? result.then(resolve, reject) : reject(
                            result);
                    } catch (err) {
                        reject(err)
                    }
                }

                switch (self.status) {
                    case PENDING:
                        //异步时执行then时,resolve还未执行就先把callback放到一个回掉函数组中,
                        //等resolve执行时,再去执行这个数组中的方法就实现了异步非pending时,就不用把回掉保存了
                        self.resolveCallback.push(
                            // 这里我们用setTimeout来模拟实现then的微任务
                            setTimeout(() => {
                                fulfilled()
                            }, 0)
                        );
                        self.rejectCallback.push(
                            setTimeout(() => {
                                rejected()
                            }, 0)
                        );
                        break;
                    case FULFILLED:
                        setTimeout(() => {
                            fulfilled();
                        })

                        break;
                    case REJECT:
                        setTimeout(() => {
                            rejected();
                        })
                        break;
                }
            })
            return promise

        }
        // Promise.prototype.catch就是Promise.prototype.then(null, onRejected)的别名

        catch (onRejected) {
            return this.then(undefined, onRejected)
        }

        //then和catch中,同样的语句需成功和失败各写一次,但finally只需一次,无参数,会将结果和error传递
        finally(callback) {
            return this.then(
                value => MyPromise.resolve(callback()).then(() => value),
                error => MyPromise.resolve(callback()).then(() => {
                    throw error
                })
            )
        }
        /**
         * Promise.all() 接受一个数组,返回一个promise对象
         *    所有的promise状态变为FULFILLED,返回的promise状态才变为FULFILLED。
         *     一个promise状态变为REJECTED,返回的promise状态就变为REJECTED。
         *    数组成员不一定都是promise,需要使用Promise.resolve()处理。
         */
        static all(promiseArr) {
            const len = promiseArr.length;
            const values = new Array(len);

            let count = 0; // 记录已经成功的promise个数
            return new MyPromise((resolve, reject) => { //传进来的数组不一定都是p对象,需把不是p的数字包装成p
                for (let i = 0; i < len; i++) {
                    // Promise.resolve()处理,确保每一个都是promise实例,若不是的话值将被忽略,但仍包含在返回的p的数组中
                    MyPromise.resolve(promiseArr[i]).then(
                        val => {
                            values[i] =
                                val; // 这里用索引别用push,保证返回的顺序,即p1在前,即便p1的结果获取的比p2要晚。p1的返回数据也会在p2前面。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,
                            // 成功时返回的是一个结果数组,而失败则返回最先被reject失败状态的值
                            count++;
                            if (count === len) resolve(values); // 如果全部执行完,改变promise的状态为FulFilled
                        },
                        err => {
                            reject(err)
                        }
                    )
                }
            })
        }
        static race(promiseArr) {
            return new MyPromise((resolve, reject) => {
                promiseArr.forEach(item => {
                    MyPromise,
                    resolve(item).then(
                        val => resolve(val),
                        err => reject(err)
                    )
                })
            })
        }

        static resolve(value) {
            // 若是promise实例,直接返回
            if (value instanceof MyPromise) {
                return value;
            } else {
                // 若不是promise实例,返回一个新的promise对象,状态为fulfilled
                //1、参数是一个thenable对象,,转为Promise后执行该对象的then方法
                //2、没有参数,直接返回一个resolved状态的promise
                //3、参数是一个原始值,返回一个新的Promise,状态为resolved
                return new MyPromise((resolve, reject) => resolve(value))
            }
        }
        static reject(reason) {
            // Promise.reject方法的参数会原封不动地作为reject的参数
            return new MyPromise((resolve, reject) => reject(reason))
        }
    }

async/await

await,它先计算出右侧结果,并暂时中断async函数、被阻塞后,要执行async之外的代码

async/Await 如何通过同步的方式实现异步

async/await 的错误捕获方式

主要是用try/catch

//单个异步操作
 (async () => {
    try {
      const data = await fetchData() 
      console.log('data is ->', data)
    } catch (err) {
      console.log('err is ->', err)
    }
  })()
//有多个异步操作时
//`async/await` 本质就是 `promise` 的语法糖,既然是 `promise` 那么就可以使用 `then` 函数了
(async () => {
    const fetchData = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('fetch data is me')
            }, 1000)
        })
    }

    // 抽离成公共方法
    const awaitWrap = (promise) => {
        return promise
            .then(data => [null, data])
            .catch(err => [err, null])
    }

    const [err, data] = await awaitWrap(fetchData())
    console.log('err', err)
    console.log('data', data)
    // err null
    // data fetch data is me
})()

关于输出顺序问题

参考链接

zhuanlan.zhihu.com/p/172378607

promise中没有resolve或者reject、状态还是pendingpromise.then并不会执行,它只有在被改变了状态之后才会执行

   const promise1 = new Promise((resolve, reject) => {
        console.log('p1')
    })
     promise1.then(() => {
        console.log(3)
    })
    console.log('1', promise1)
   
// p1 
// 1 Promise { <pending> }

关于函数是否有return时,不管是否有return,首先要看该函数在什么时候调用,只有在函数调用的时候才会执行

const fn = () => {  
  return new Promise((resolve, reject) => {
    console.log(1)
    resolve('success')
  })
}
或:
//const fn = () => (new Promise((resolve, reject) => {}))

console.log('start')

fn().then(res => {
  console.log(res)
})
注意:不要看到`new Promise()`,就以为执行它的第一个参数函数,我们还需要注意它是不是被包裹在函数当中,如果是的话,只有在函数调用的时候才会执行。

//"start"、1、"success"

构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用 ,Promise的状态一经改变就不能再改变

catch不管被连接到哪里,都能捕获上层未捕捉过的错误、catch()也会返回一个Promise、如果Promise没有返回值,打印出来的是undefined,如果有就把结果传递下去

tips:catch后的.then()会被执行

 const promise = new Promise((resolve, reject) => {
        reject('error')
        resolve('success2')
    })

    promise.then(res => {
        console.log('then1: ', res)
    }).catch(err => {
        console.log('catch: ', err)
        return 66
    }).then(res => {
        console.log('then3: ', res)
    }).catch(err => {
        console.log('catch2: ', err)
    })

    //catch: error
    //then3: 66

Promise可以链式调用,不过promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般我们任务的链式调用一样return this

 Promise.reject(1)
    .then(res => {
        console.log(res)
        return 2
    })
    .catch(err => {
        console.log(err)
        return 3
    })
    .then(res => {
        console.log(res)
        return new Error('error!!!')//返回任意一个非 p的值都会被包裹成 p对象,因此该句被包裹成了`return Promise.resolve(new Error('error!!!'))`
    })
    .then(res => {
        console.log(res)
    })
    .catch(err => {
        console.log(err)
    })
  //1、3、Error: error!!!
//因为reject(1),此时走的是catch,且第二个then中的res得到的就是catch中的返回值。

then中有两个函数可以用来处理成功和失败

Promise.reject('err!!!')
  .then(
    res => {
      console.log('success', res)
    },
    err => {
      console.log('error', err)
    }
  )
  .catch(err => {
    console.log('catch', err)
  })

答案:'error' 'error!!!'

Promise.resolve()
  .then(
    function success(res) {
      throw new Error('error!!!')
    },
    function fail1(err) {
      console.log('fail1', err)
    }
  )
  .catch(function fail2(err) {
    console.log('fail2', err)
  })
//由于Promise调用的是`resolve()`,因此.`then()`执行的应该是`success()`函数,可是success()函数抛出的是一个错误,它会被后面的`catch()`给捕获到,而不是被fail1函数捕获

fail2 Error: error!!! 

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环,因此结果会报错

const promise = Promise.resolve().then(() => {
  return promise
})
promise.catch(console.err)
//UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传,会把值直接传导最后一个then里

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
//1

关于finally知识点:

  • 不管P对象最后的状态如何都会执行
  • .finally()方法的回调函数不接受任何的参数,即在.finally()函数中是没法知道P最终的状态是resolved还是rejected的
  • 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的P对象
  function promise1() {
        let p = new Promise(resolve => {
            console.log('promise1')
            resolve('1')
        })
        return p
    }

    function promise2() {
        return new Promise((resolve, reject) => {
            reject('error')
        })
    }

    promise1()
        .then(res => console.log(res))
        .catch(err => console.log(err))
        .finally(() => console.log('finally1'))

    promise2()
        .finally(() => {
            console.log('finally2')
            return '我是finally2返回的值'//就算返回了新的值,它后面的catch函数接收到的结果还是error
        })
        .then(res => console.log(res))
        .catch(err => console.log(err))

// 首先定义两个函数p1,p2
//p1()先被调用,执行里面new Promise的同步代码,打印出p1,遇到resolve('1'),将p的状态改为了resolved并将结果保存下来。这时p1代码执行完了
//然后执行p1().then(),将此条微任务加入本轮的微任务列表
//tips:代码并不会接着往链式调用的下面走,即不会先将.finally加入微任务列表,因为.then本身是微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行

//接着往下执行p2(),执行reject('error')的时候将promise2函数中的Promise的状态变为了rejected,这时候p2代码执行完了
//然后执行p2().catch(),将其加入当前的微任务队列,且链式调用后面的内容得等该任务执行完后才执行
//这时本轮的任务执行完了,接着看微任务里的列表

//打印出p1().then(),打印出1、、然后遇到finally(),加入到微任务列表
//在执行p2().finally(),打印出finally2、然后遇到第二个catch(),加入到微任务列表
//本轮任务执行完,接着在查看微任务列表,依次执行finally1和error

//执行结果:promise1、 1 、finally2、finally1 、error

finally不管Promise的状态是resolved还是rejected都会执行,且它的回调函数是接收不到Promise的结果的,所以finally()中的res是迷惑项

最后一个定时器打印出的p1其实是.finally的返回值,我们知道.finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值,而这道题中.finally上一个Promise是.then(),但是这个.then()并没有返回值,所以p1打印出来的Promise的值会是undefined,如果有返回值2,则打印出来、结果如果你在定时器的下面加上一个return 1,则值就会变成1。

const p1 = new Promise(resolve => {
  setTimeout(() => {
    resolve('resolve3')
    console.log('timer1')
      }, 0)
  resolve('resovle1')
  resolve('resolve2')
})
  .then(res => {
    console.log(res)
    setTimeout(() => {
      console.log(p1)
    }, 1000)
   return 2//Promise {<fulfilled>: 2}

  })
  .finally(res => {
    console.log('finally', res)
  })
//  结果:
resovle1
finally undefined
timer1
Promise {<fulfilled>: undefined}

async/await相关:async中的await命令是一个Promise对象,返回该对象的结果。若不是Promise对象的话,就会直接返回对应的值,相当于Promise.resolve()

若没有resolve,reject改变p状态,即await后面的Promise是没有返回值的,它的状态始终是pending状态,因此相当于一直在await却始终没有响应...,所以在await之后的内容是不会执行的

 async function async1() {
    console.log(1)
    await new Promise(resovle=> {
        console.log(2)
        // resovle(3)
    })
    console.log(4)
    return 5
}

console.log(6)

async1().then(res => console.log(res))//在async1中的`new Promise` resovle的值,但如果没有改变pending状态的resolve的话,那和`async1().then()`里的值是没有关系的

console.log(7)
//612745
 async function testSometing() {
        console.log('1')
        return Promise.resolve('4') //微任务,放到微任务列表等待下次执行
    }

    async function testAsync() {
        console.log('3')
        return Promise.reject('4') //失败状态,不往下执行
    }

    async function test() {
        console.log('5')
        const v1 = await testSometing() //会阻塞下面的代码,把他们放在一个微任务中
        console.log(v1)
        const v2 = await testAsync()//接收到失败值的话,下面的代码就不再执行
        console.log(v2)
        console.log(v1,v2)
    }

    test()

    var promise = new Promise(resolve => {
        console.log('6')
        resolve('7')
    })
    promise.then(val => console.log(val))

    console.log('8')
    
//    结果:5168743、
//Uncaught (in promise) 4
    async function async1() {
      try {
        await Promise.reject('error!!!')//微任务放到微任务列表,下次执行
      } catch (e) {
        console.log(e)//捕获错误error!!!
      }
      console.log('async1')
      return Promise.resolve('async1 success')
    }
    async1().then(res => console.log(res))
    console.log('script start')

    //结果:
    script start
    error!!!
    async1
    async1 success

then方法内的函数的返回值类型,决定下一个then方法内部的状态, 所以如果then方法内有return, 就需要把return后面的表达式执行完毕后, 才能再调用下一个then方法; 第一个Promise的第二个then方法相当于是挂在这个return返回值上的

new Promise((resolve, reject) => {
    console.log("1");
    resolve();
  })
.then(() => {
    console.log("2");
    return new Promise((resolve, reject) => {
        console.log("3");
        resolve();
    })
    .then(() => {
        console.log("4");
    })
    .then(() => {
        console.log("5");
    });
})
.then(() => {
    console.log("6");
});
//123456

总结:

  • Promise构造函数接收的参数是一个同步任务, 需要同步执行的,Promise实例的then方法是一个微任务, 而且then方法的调用是在Promise构造函数执行完毕之后; 如果在执行微任务的过程中,又产生了新的微任务,会直接将该微任务添加至微任务队列末尾, 并且会在当前事件循环结束之前执行掉

image.png Promise 并行执行和顺序执行

Array.prototype.includes()方法

来判断一个数组是否包含一个指定的值,返回布尔值

const arr = [1, 3, 5, 2, '8', NaN, -0]
arr.includes(1) // true
arr.includes(NaN) // true
arr.includes(+0) // true

indexOf() 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1

内部使用严格相等运算符(===)进行判断,导致对NaN的误判

[NaN].indexOf(NaN)// -1

find() 和 findIndex() 数组实例的find方法,用于找出第一个符合条件的数组成员

[1, 4, -5, 10].find((n) => n < 0) // -5
[1, 5, 10, 15].findIndex(function(value) {
  return value > 9;
}) // 2
[NaN].findIndex(y => Object.is(NaN, y)) // 0

Array.prototype.flat()

会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

newArray = arr.flat(depth) // depth是指定要提取嵌套数组的结构深度,默认值为 1
const numbers1 = [1, 2, [3, 4, [5, 6]]]
console.log(numbers1.flat())// [1, 2, 3, 4, [5, 6]]
console.log(numbers2.flat(2))// [1, 2, 3, 4, 5, 6]

Promise 利用了三大技术手段来解决回调地狱:

  1. 回调函数延迟绑定:回调函数不是直接声明的,而是在通过后面的 then方法传入的,即延迟传入
  2. 返回值穿透。
  3. 错误冒泡:分别处理成功和失败
readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
}).catch(err => {
  // xxx
})
//这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了
- 实现链式调用,解决多层嵌套问题
- 实现错误冒泡后一站式处理,解决每次任务中判断错误、增加代码混乱度的问题

一旦其中有一个PENDING状态的 Promise 出现错误后状态必然会变为失败, 然后执行 onRejected函数,而这个 onRejected 执行又会抛错,把新的 Promise 状态变为失败,新的 Promise 状态变为失败后又会执行onRejected......就这样一直抛下去,直到用catch 捕获到这个错误,才停止往下抛。 这就是 Promise 的错误冒泡机制

promise

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then、catch、finally方法会返回一个新的Promise所以可以允许我们链式调用,解决了传统的回调地狱问题

ES6规定,Promise对象是一个构造函数,用来生成Promise实例

image.png Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。这么说用Promise new出来的对象肯定就有then、catch方法

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数

 var p = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
            console.log('执行完成');
            resolve('数据');
        }, 2000);
    });
//2秒后,输出“执行完成”,并且调用resolve方法

tips:只是new了一个对象,并没有调用它,我传进去的函数就已经执行了,这是需要注意的一个细节。所以用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('数据');
        }, 2000);
    });
    return p;            
}
runAsync()

//直接在脚本文件中定义一个Promise,它构造函数的第一个参数是会立即执行的:
const p1 = new Promise(r => console.log('立即打印'))
//因此为了控制它什么时候执行,我们可以用一个函数包裹着它,在需要它执行的时候,调用这个函数就可以了:
function runP1 () {
    const p1 = new Promise(r => console.log('立即打印'))
    return p1
}
runP1() // 调用此函数时才执行

Promise 的本质是一个有限状态机,存在三种状态:

  • PENDING(等待)
  • FULFILLED(成功)
  • REJECTED(失败) 对于 Promise 而言,状态的改变不可逆,即由等待态变为其他的状态后,就无法再改变了

特点

  1. Promise的状态一经改变就不能再改变
const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  })
//结果 : "then: success1"
//说明构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用

2. .then和.catch都会返回一个新的Promise

  1. catch不管被连接到哪里,都能捕获上层未捕捉过的错误
const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })
//结果:"catch: " "error"
"then3: " undefined

4. 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如以下return 2会被包装为return Promise.resolve(2)

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);//得到的是上个then中的值
  });
  //输出结果之所以依次打印出1和2,那是因为resolve(1)之后走的是第一个then方法,并没有走catch里,所以第二个then中的res得到的实际上是第一个then的返回值。且return 2会被包装成resolve(2)
 // tips:如果把Promise.resolve(1)改为Promise.reject(1)又会怎么样呢?打印1 和 3,因为reject(1)此时走的就是catch,且第二个then中的res得到的就是catch中的返回值

5. Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    resolve('success')
  }, 1000)
})
const start = Date.now();
promise.then(res => {
  console.log(res, Date.now() - start)
})
promise.then(res => {
  console.log(res, Date.now() - start)
})
//结果:
'timer'
'success' 1001
'success' 1002

  1. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
//结果:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

7. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传

Promise.resolve(1)
  .then(2)//数字类型
  .then(Promise.resolve(3))//对象类型
  .then(console.log)
//结果:1

8. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
//结果:'error' 'error!!!'

但另外一个案例:

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })
//结果:fail2 Error: error!!!
		at success
//由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获

9. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})
//返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))
//所以输出的走的是.then里面:"then: " "Error: error!!!"
如果你抛出一个错误的话,可以用下面👇两的任意一种:
return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')

实现红绿灯交替重复亮

function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}
const light = function (timer, cb) {
  return new Promise(resolve => {
    setTimeout(() => {
      cb()
      resolve()
    }, timer)
  })
}
const step = function () {
  Promise.resolve().then(() => {
    return light(3000, red)
  }).then(() => {
    return light(2000, green)
  }).then(() => {
    return light(1000, yellow)
  }).then(() => {
    return step()
  })
}
step();

Promise.race()

作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃

Promise.all()

接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃

核心功能:

  1. 传入参数为一个空的可迭代对象,则直接进行resolve。
  2. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败。
  3. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
//以下函数传入一个值x,然后间隔一秒后打印出这个x
function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}
//用.all执行时:
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))
//输出结果:在间隔一秒后,控制台会同时打印出1, 2, 3,还有一个数组[1, 2, 3]

有了all就可以并行执行多个异步操作,且在一个回调中处理所有的返回数据

`.all()后面的.then()里的回调函数接收的就是所有异步操作的结果`
//Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致

而且这个结果中数组的顺序和Promise.all()接收到的数组顺序一致!!!
//案例:
const wait = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`wait ${ms}ms`)
        resolve()
    }, ms)
})
const PA = Promise.all([wait(3000), wait(1000), wait(2000)])
// 依次打印:wait 1000ms wait 2000ms wait 3000ms

//async-await 同时触发多个异步操作示例:
const wait = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`wait ${ms}ms`)
        resolve()
    }, ms)
})
;(async () => {
    await Promise.all([wait(3000), wait(1000), wait(2000)])
    // 依次打印:wait 1000ms wait 2000ms wait 3000ms
})()

//或者:
const arr = [1, 2, 3]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x)), 1000)
    })
  })
}, Promise.resolve())
//更简单一点写:
const arr = [1, 2, 3]
arr.reduce((p, x) => p.then(() => new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve())


//all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行

all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行

怎么保证Promise.all中某请求异常,依然能获取其他成功请求的返回值?

控制并发请求

function request(urls,maxNumber,callback),根据urls数组内的url地址进行并发网络请求,最大并发数maxNumber,当所有请求完毕后调用callback函数

场景:promises数组中每个对象都是http请求,或每个对象包含了复杂的调用处理。而这样的对象有几十万个。那么在瞬间发出几十万http请求(tcp连接数不足可能造成等待),或者堆积了无数调用栈导致内存溢出。此时就需考虑对Promise.all做并发限制

Promise.all并发限制指的是每个时刻并发执行的promise数量是固定的,最终的执行结果还是保持与原来的Promise.all一致

promise并不是因调用Promise.all才执行,而是在实例化promise对象的时候就执行了,在理解这一点的基础上,要实现并发限制,只能从promise实例化上下手,即把生成promises数组的控制权,交给并发控制逻辑

 function asyncPool(poolLimit, array, iteratorFn) {
        let i = 0;
        const ret = [], executing = [];
        const enqueue = function () {
            if (i === array.length) {return Promise.resolve();}// 边界处理,array为空数组
            // 每调一次enqueue,初始化一个promise
            const item = array[i++];
            const p = Promise.resolve().then(() => iteratorFn(item, array));
            ret.push(p);// 放入promises数组
            // promise执行完毕,从executing数组中删除
            const e = p.then(() => executing.splice(executing.indexOf(e), 1));
            // 插入executing数字,表示正在执行的promise
            executing.push(e);
            // 用Promise.rece,每当executing数组中promise数量低于poolLimit,就实例化新的promise并执行
            let r = Promise.resolve();
            if (executing.length >= poolLimit) {r = Promise.race(executing);}
            // 递归,直到遍历完array
            return r.then(() => enqueue());
        };
        return enqueue().then(() => Promise.all(ret));
    }

正在处理的数量,已经处理完的数量

因为是promise加上递归,所以在代码注释上不太好标注执行顺序,但是大概的逻辑可以总结为: 从array第1个元素开始,初始化promise对象,同时用一个executing数组保存正在执行的promise 不断初始化promise,直到达到poolLimt

使用Promise.race,获得executing中promise的执行情况,当有一个promise执行完毕,继续初始化promise并放入executing中

所有promise都执行完了,调用Promise.all返回

Promise的实现

实现原理:其实就是一个发布订阅者模式

  1. 构造函数接收一个 executor 函数,并会在 new Promise() 时立即执行该函数
  2. then 时收集依赖,将回调函数收集到 成功/失败队列
  3. executor 函数中调用 resolve/reject 函数
  4. resolve/reject 函数被调用时会通知触发队列中的回调
const isFunction = variable =>typeof variable === 'function';

// 定义Promise的三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  // 构造函数,new 时触发
  constructor(handle: Function) {
    try {
      handle(this._resolve, this._reject);
    } catch (err) {
      this._reject(err);
    }
  }
  // 状态 pending fulfilled rejected
  private _status: string = PENDING;
  // 储存 value,用于 then 返回
  private _value: string | undefined = undefined;
  // 失败队列,在 then 时注入,resolve 时触发
  private _rejectedQueues: any = [];
  // 成功队列,在 then 时注入,resolve 时触发
  private _fulfilledQueues: any = [];
  // resovle 时执行的函数
  private _resolve = val => {
    const run = () => {
      if (this._status !== PENDING) return;
      this._status = FULFILLED;
      // 依次执行成功队列中的函数,并清空队列
      const runFulfilled = value => {
        let cb;
        while ((cb = this._fulfilledQueues.shift())) {
          cb(value);
        }
      };
      // 依次执行失败队列中的函数,并清空队列
      const runRejected = error => {
        let cb;
        while ((cb = this._rejectedQueues.shift())) {
          cb(error);
        }
      };
      /*
       * 如果resolve的参数为Promise对象,
       * 则必须等待该Promise对象状态改变后当前Promsie的状态才会改变
       * 且状态取决于参数Promsie对象的状态
       */
      if (val instanceof MyPromise) {
        val.then(
          value => {
            this._value = value;
            runFulfilled(value);
          },
          err => {
            this._value = err;
            runRejected(err);
          }
        );
      } else {
        this._value = val;
        runFulfilled(val);
      }
    };
    // 异步调用
    setTimeout(run);
  };
  // reject 时执行的函数
  private _reject = err => {
    if (this._status !== PENDING) return;
    // 依次执行失败队列中的函数,并清空队列
    const run = () => {
      this._status = REJECTED;
      this._value = err;
      let cb;
      while ((cb = this._rejectedQueues.shift())) {
        cb(err);
      }
    };
    // 为了支持同步的Promise,这里采用异步调用
    setTimeout(run);
  };
  // then 方法
  then(onFulfilled?, onRejected?) {
    const { _value, _status } = this;
    // 返回一个新的Promise对象
    returnnew MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封装一个成功时执行的函数
      const fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value);
          } else {
            const res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err);
        }
      };

      // 封装一个失败时执行的函数
      const rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error);
          } else {
            const res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err);
        }
      };

      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
          this._fulfilledQueues.push(fulfilled);
          this._rejectedQueues.push(rejected);
          break;
        // 当状态已经改变时,立即执行对应的回调函数
        case FULFILLED:
          fulfilled(_value);
          break;
        case REJECTED:
          rejected(_value);
          break;
      }
    });
  }
  // catch 方法
  catch(onRejected) {
    returnthis.then(undefined, onRejected);
  }
  // finally 方法
  finally(cb) {
    returnthis.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason =>
        MyPromise.resolve(cb()).then(() => {
          throw reason;
        })
    );
  }
  // 静态 resolve 方法
  static resolve(value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value;
    returnnew MyPromise(resolve => resolve(value));
  }
  // 静态 reject 方法
  static reject(value) {
    returnnew MyPromise((resolve, reject) => reject(value));
  }
  // 静态 all 方法
  static all(list) {
    returnnew MyPromise((resolve, reject) => {
      // 返回值的集合
      let values = [];
      let count = 0;
      for (let [i, p] of list.entries()) {
        // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
        this.resolve(p).then(
          res => {
            values[i] = res;
            count++;
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values);
          },
          err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err);
          }
        );
      }
    });
  }
  // 添加静态race方法
  static race(list) {
    returnnew MyPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
        this.resolve(p).then(
          res => {
            resolve(res);
          },
          err => {
            reject(err);
          }
        );
      }
    });
  }
}

Promise A+ 规范

async/await

async 函数,就是 Generator 函数的语法糖,隐式地返回一个Promise

async 函数的实现就是将Generator函数和自动执行器,包装在一个函数里,返回promise,可直接使用 then() 方法进行调用。可继续操作

async函数接收到返回的值,发现不是异常或者reject,则判定成功,这里可以return各种数据类型的值,false,NaN,undefined...总之,都是resolve 但是返回如下结果会使async函数判定失败reject

内部含有直接使用并且未声明的变量或者函数。

  • 内部抛出一个错误throw new Error或者返回reject状态return Promise.reject('执行失败')
  • 函数方法执行出错(🌰:Object使用push())等等...

在async里,必须要将结果return回来,不然的话不管是执行reject还是resolved的值都为undefine,建议使用箭头函数

实际上await是一个让出线程的标志。await后面的函数会先执行一遍(比如await Fn()的Fn ,并非是下一行代码),然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await****后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入Promise队列(Promise的Job Queue)

async/await 和 generator 方案,相较于 Promise 而言,有一个重要的优势:Promise 的错误需要通过回调函 数捕获,try catch 是行不通的。而 async/await 和 generator 允许 try/catch

async function async1() {
  console.log("async1 start");
  // 原来代码
  // await async2();//会阻塞async1后面代码的执行,因此会先去执行async2中的同步代码async2,然后跳出async1
  // console.log("async1 end");
  
  //紧跟着await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中
  // 转换后代码
  new Promise(resolve => {
    console.log("async2")
    resolve()
  }).then(res => console.log("async1 end"))
}
async function async2() {
  console.log("async2");
}
async1();
console.log("start")//在一轮宏任务全部执行完之后,再来执行刚刚await后面的内容async1 end
//输出结果
'async1 start'
'async2'
'start'
'async1 end'
 async function async1() {
        console.log('async1 start');
        await new Promise(resolve => {
            console.log('promise1')
            resolve()//当没有这句时,那就是没有返回值的,即它的状态始终是pending状态,因此相当于一直在await,await,await却始终没有响应...、所以在await之后的内容是不会执行的,也包括async1后面的 .then,也就不会输出最后两个
        })
        console.log('async1 success');
        return 'async1 end'
    }
    console.log('srcipt start')
    async1().then(res => console.log(res))
    console.log('srcipt end')
    
//输出结果
'script start'
'async1 start'
'promise1'
'script end'
async1 success
async1 end

特点:

  • 和 promise 一样,是非阻塞的。但不用写 then 及其回调函数
  • 使异步代码在形式上更接近于同步代码
  • await 是个运算符,用于组成表达式,它会阻塞后面的代码。如果等到的是 Promise 对象,则得到其 resolve 值。否则,会得到一个表达式的运算结果

async处理错误

在async中,如果 await后面的内容是一个异常或者错误的话,会怎样呢?

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))
//如果在async函数中抛出了错误,则终止错误结果,不会继续向下执行。
//所以输出结果
'async2'
Uncaught (in promise) error

或者:
async function async1 () {
  console.log('async1');
  throw new Error('error!!!')
  return 'async1 success'
}
async1().then(res => console.log(res))
结果:'async1'
Uncaught (in promise) Error: error!!!

想使错误的地方不影响async函数后续的执行的话,可用try catch

async function async1 () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
//  或者:
// await Promise.reject('error!!!')
  //  .catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
//输出
'script start'
'error!!!'
'async1'
'async1 success'

优化多个await

使用 async/await 等待异步操作完成的时候,如果前后两个异步操作不存在依赖关系,同时触发应该是更好的方案、因为 await 后面必须跟一个 Promise 实例,于是可以用 Promise.all() 这个方法把多个 Promise 实例合并成一个 Promise 实例

实现async/await

原理就是利用 generator(生成器)分割代码片段。然后我们使用一个函数让其自迭代,每一个yield 用 promise 包裹起来。执行下一步的时机由 promise 来控制 async/await 是关键字,不能重写它的方法,我们使用函数来模拟

异步迭代,模拟异步函数

function _asyncToGenerator(fn) {
  returnfunction() {
    var self = this,
      args = arguments;
    // 将返回值promise化
    returnnewPromise(function(resolve, reject) {
      // 获取迭代器实例
      var gen = fn.apply(self, args);
      // 执行下一步
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
      }
      // 抛出异常
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
      }
      // 第一次触发
      _next(undefined);
    });
  };
}

执行迭代步骤,处理下次迭代结果

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    // 迭代器完成
    resolve(value);
  } else {
    // -- 这行代码就是精髓 --
    // 将所有值promise化
    // 比如 yield 1
    // const a = Promise.resolve(1) a 是一个 promise
    // const b = Promise.resolve(a) b 是一个 promise
    // 可以做到统一 promise 输出
    // 当 promise 执行完之后再执行下一步
    // 递归调用 next 函数,直到 done == true
    Promise.resolve(value).then(_next, _throw);
  }
}
//测试 _asyncToGenerator
const asyncFunc = _asyncToGenerator(function*() {
  const e = yieldnewPromise(resolve => {
    setTimeout(() => {
      resolve('e');
    }, 1000);
  });
  const a = yieldPromise.resolve('a');
  const d = yield'd';
  const b = yieldPromise.resolve('b');
  const c = yieldPromise.resolve('c');
  return [a, b, c, d, e];
});

asyncFunc().then(res => {
  console.log(res); // ['a', 'b', 'c', 'd', 'e']
});

async 相较于 Promise 的优势

  1. 相比于 Promise,它能更好地处理 then 链
  function step1(n) {
        console.log(`step1 with ${n}`);
        return takeLongTime(n);
    }

    function step2(n) {
        console.log(`step2 with ${n}`);
        return takeLongTime(n);
    }

    function step3(n) {
        console.log(`step3 with ${n}`);
        return takeLongTime(n);
    }

    function takeLongTime(n) {
        return new Promise(resolve => {
            setTimeout(() => resolve(n + 200), n);
        });
    }

现在用 Promise 方式来实现这三个步骤的处理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900

如果用 async/await 来实现的话,会是这样:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
}
doIt();

  1. 中间值 现在要实现每一个步骤都需要之前每个步骤的结果。
//Pomise的实现看着很晕,传递参数太过麻烦
function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
//用 async/await 来写:
async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
}
doIt();
//没有多余的中间值,更加优雅地实现了

3. 调试 相比于 Promise 更易于调试、因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,因为调试器只能跟踪同步代码的每一步

image.png

image.png

限制异步操作的并发个数并尽可能快的完成全部

已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject、 保证每次并发请求的数量为3,那么我们可以先请求urls中的前面三个(下标为0,1,2),并且请求的时候使用Promise.race()来同时请求,三个中有一个先完成了(例如下标为1的图片),我们就把这个当前数组中已经完成的那一项(第1项)换成还没有请求的那一项(urls中下标为3)、直到urls已经遍历完了,然后将最后三个没有完成的请求(也就是状态没有改变的Promise)用Promise.all()来加载它们 image.png

  var urls = [
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
        "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
    ];

    function loadImg(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = function () {
                console.log("一张图片加载完成");
                resolve(img);
            };
            img.onerror = function () {
                reject(new Error('Could not load image at' + url));
            };
            img.src = url;
        })
    };
// 以下是具体的实现流程 
    function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 复制urls
  // 这一步是为了初始化 promises 这个"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下标是为了知道数组中是哪一项最先完成
      return index;
    });
  });
  // 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已经完成的下标
        })
        .then(fastestIndex => { // 获取到已经完成的下标
        	// 将"容器"内已经完成的那一项替换
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要继续将这个下标返回,以便下一次变量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化传入
    .then(() => { // 最后三个用.all来调用
      return Promise.all(promises);
    });
}
limitLoad(urls, loadImg, 3)
  .then(res => {
    console.log("图片全部加载完毕");
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });