初探Promise 中断与异常传送

Posted ltfxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初探Promise 中断与异常传送相关的知识,希望对你有一定的参考价值。

Promise

Promise是javascript ES6对于异步任务的解决方案

从语法上来说,Promise是一个构造函数,通过new关键字来新建对象

从功能上来说,Promise用来封装一个异步操作,无论异步操作是成功或失败,Promise都将承诺给你返回一个确切的答案,一个异步任务最终执行的结果。

为什么要有promise

先谈谈为什么要有异步。js单线程,一次只能执行一个任务,有些任务是耗时的,比如图片加载,网络请求等,这时候如果同步执行的话,效率会非常低下。此时就需要异步来处理了。

异步主要是解决了同步阻塞的情况。但是在异步处理中也出现了很多问题,于是ES6对这一问题提出了解决方案---Promise

1 指定回调函数的方式更加灵活

旧的方式:必须在启动异步任务前指定回调函数

promise:启动异步任务-->返回promise对象-->给promise对象绑定回调函数(甚至可以在异步开始前或结束后指定,通过then方法)

你可以先改变状态,再指定then中的回调去处理Promise的结果,那么当状态改变,then中的回调就会执行。

你也可以先指定then中的回调,那么当状态改变时,then中的回调就会执行。

而传统方式必须在启动异步任务时就指定好要去调用的回调函数

        const promise = new Promise((resolve, reject) => {
            //executor执行器函数,同步的
            console.log(‘excutor‘)
            setTimeout(() => {
                let date = Date.now();
                if (date % 2 === 0) {
                    resolve(date)
                } else {
                    reject(date)
                }
            }, 1000)
        })

        console.log(‘创建promise之后‘)

        //在创建promise之后指定promise的回调函数,onResolved和onRejected
        //所谓的promise更加灵活,就是指你可以在创建promise之后再通过then这样的api去指定promise的回调函数
        //而传统方式必须在启动异步任务前就指定好要调用的回调函数
        setTimeout(() => {
            promise.then(
                (value) => {
                    console.log("成功" + value)//onFulfiled
                },
                (reason) => {
                    console.log("失败" + reason)//onRejected
                }
            )
        }, 3000)

2 解决回调地狱问题

回调地狱:或者说回调金字塔。就是在异步的回调函数里面开启下一个异步操作,执行下一个异步函数。多个串联的异步操作。

promise可以解决回调金字塔的问题,或者通俗一点说,就是通过链式编程解决了回调函数嵌套过多的问题,回调地狱不便于阅读,也不利于异常处理

举个例子,要想实现图片的预加载,且保证先后顺序一致,那么就必须结合onload事件,在这个事件的回调函数里面再改变src从而加载下一张图片。要实现这个功能,你的构思可能如下

    let base = 100;
    let img = new Image()
    img.src = ‘./img/icon-0.png‘;
    img.onload = function () {
        console.log(base += img.width);
        let img1 = new Image()
        img1.src = ‘./img/icon-1.png‘;
        img1.onload = function () {
            console.log(base += img1.width);
            let img2 = new Image();
            img2.src = ‘./img/icon-2.png‘;
            img2.onload = function () {
                console.log(base += img2.width);
                let img3 = new Image();
                img3.src = ‘./img/icon-3.png‘;
            }   
        }
    }

以上代码能不能解决预加载问题?当然可以。但是问题也同样显著存在,那就是如果图片数量级过大,就会造成函数嵌套过深的问题,也就是上面提到的回调金字塔。

Promise很好地解决了这个问题。

Promise 处理图片预加载问题


    function loadImage(src) {
        return new Promise(function (resolve, reject) {
            let img = new Image();
            img.src = src;
            img.onload = function () {
                resolve(img)
            }
            img.onerror = function () {
                reject(img.src, ‘加载失败‘)
            }
        })
    }

    //预加载多张图片,采用promise的then返回一个新的promise实现链式编程
    loadImage(‘./img/icon-0.png‘)
        .then(function (img) {
        console.log(img)
        return loadImage(‘./img/icon-1.png‘)
    }, function (src) { })
        .then(function (img) {
        console.log(img)
        return loadImage(‘./img/icon-2.png‘)
    }, () => { })
        .then(function (img) {
        console.log(img)
    }, () => { })

关于回调函数

即然Promise旨在解决回调地狱问题,那么就不得不谈谈回调函数

回调函数有两种类型,一种是同步回调,一种是异步回调

同步回调函数与异步回调函数

同步回调

立即执行,完全执行完了才结束,不会放入回调队列中

例如:数组遍历相关的回调函数或Promise的excutor函数

异步回调

不会立即执行,会放入回调队列中执行

例如:定时器回调/ajax回调/promise成功/失败的回调

        //同步回调,一些数组方法如foreach、map
        let arr = [1, 2, 3]
        arr.forEach(v => console.log(v))
        arr.map(v => console.log(v))
        console.log(‘after forEach‘)
        /*
            1
            2
            3
            after forEach
         */

        //异步回调,定时器回调、ajax回调、promise成功或失败的回调
        setTimeout(() => { console.log(‘timeout‘) }, 0)
        console.log(‘after timeout‘)
         /*
           0
            timeout
         */

Primise的状态与状态改变

  • pending 等待
  • resolved 成功
  • rejected 失败

状态改变只能由一次,且只能由pending转变为resolved或rejected

成功时返回的值叫做value,失败时返回的值叫做reason

状态改变

  • 执行resolve将pending改为resolved
  • 执行reject将pending改为rejected
  • 抛出异常,pending会变为rejected,然而,reject并不意味着程序一定有异常(catch&reject)

Promise流程与执行顺序

流程

通过Promise构造函数new一个Promise对象,传一个执行器函数(启动是同步的)作为形参,在这个函数中执行异步操作

如果成功了,执行resolve,Promise对象变为resolved状态,通过then回调onResolved(onFulfild)拿到value,并返回一个新的Promise对象

如果失败了,执行reject,同时promise对象变为rejected状态,通过then或catch回调onRejected()拿到reason,并返回一个新的Promise对象

返回一个新的promise对象可以做链式编程操作

技术图片

顺序

执行器函数同步执行,resolve和then异步执行

因此打印顺序是:

executor
promise 
then 
1
        const p = new Promise((resolve, reject) => {
            console.log(‘executor‘)
            resolve(1)
        })
        console.log(‘promise ~~‘)
        p.then(
            value => { console.log(value) },//onFulfiled
            reason => { }//onRejected
        )
        console.log(‘then~~‘)

Promise API

  • Promise构造函数 Promise(executor(resolve,reject){ //三个状态})
  • Promise.prototype.then then本身是同步的,它返回一个promise,then中的回调函数是异步回调函数,
  • Promise.prototype.catch
  • Promise.race: (promises=>{}) promises包含n个promise的数组
  • Promise.all:(promises=>{}) promises包含n个promise的数组

resolve,reject,race,all是函数属性,构造函数本身就具有的

then,catch是原型属性,是promise实例具有的属性

all和race的异同

同:

都返回一个新的promise对象,都接收一个包含多个peromise对象的数组作为回调函数的形参

异:

all是所有promise全部成功才成功。race,就像单词意思一样,竞赛,不论成功或失败,谁先完成就返回谁的状态

resolve reject then catch的使用

    //生成一个成功值是1的promise
    const p1 = new Promise((resolve,reject)=>{
        resolve(1)
    })
    //生成一个成功值是2的promise
    const p2 = Promise.resolve(2);
    // console.log(p,p1)
    //生成一个失败值是3的promise
    const p3 = Promise.reject(3)

    p1.then(value=>console.log(value))//1
    p2.then(value=>console.log(value))//2
    p3.then(null,reason=>console.log(reason))//3
    p3.catch(reason=>console.log(reason))//3

all和race的使用

all:所有成功才成功,有一个失败就失败,返回一个新的promise

    //生成一个成功值是1的promise
    const p1 = new Promise((resolve, reject) => {
        resolve(1)
    })
    //生成一个成功值是2的promise
    const p2 = Promise.resolve(2);
    // 生成一个失败值是3的promise
    const p3 = Promise.reject(3)

    const pAll = Promise.all([p1, p2])
    //打印3,全部成功才成功
    pAll.then(
        (values) => {
            console.log(values)//如果没有p3,返回[1,2]
        },
        (reason) => {
            console.log(reason)//如果由p3,返回3
        }
    )

race:竞赛。谁先第一个完成,就返回谁的promis状态

    //生成一个成功值是1的promise
    const p1 = new Promise((resolve, reject) => {
        resolve(1)
    })
    //生成一个成功值是2的promise
    const p2 = Promise.resolve(2);
    // 生成一个失败值是3的promise
    const p3 = Promise.reject(3)

    const pRace = Promise.race([p3,p1,p2])
    
    const arrSuc = [p1,p2,p3]//此时p1先完成,成功
    const arrFai = [p3,p2,p1]//此时p3先完成,失败
    pRace.then(
        (values)=>{
            console.log(values)//1
        },
        (reason)=>{
            console.log(reason)//3
        }
    )

关于Promise的几个疑问

什么时候才得到数据?

then是同步的,then中的回调是异步执行的,并且then的回调在promise状态改变之后才会执行,同时得到promise中异步任务的结果数据。

先改变状态还是先指定回调?这两者的先后顺序?

你可以先改变状态,再指定then中的回调,那么当状态改变,then中的回调就会执行。

你也可以先指定then中的回调,那么当状态改变时,then中的回调就会执行。

第二种情况可以通过在executor中添加定时器来实验

        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(‘executor‘)
                resolve(1)
            }, 1000)
        })
        console.log(‘promise ~~‘)
        p.then(
            value => { console.log(value) },//onFulfiled
            reason => { }//onRejected
        )
        console.log(‘then~~‘)
        // 执行顺序:
        // promise
        // then
        // executor
        // 1

Promise.then返回的结果状态由什么决定?

简而言之,就是由.then的回调函数执行返回的结果决定

  • 如果抛出异常,新promise为rejected,reason为抛出的异常

  • 如果返回的是非Promise得任意值,新promise变为resolved,value为返回的非promise的任意值

  • 如果返国的是另一个新Promise,此Promise的结果就会称为新promise的结果,这个结果可能是成功或者失败

    new Promise((resolve, reject) => {
        resolve(1)
    }).then(
        value => {
            //1 返回新的promise,这个回调返回新promise就是下个then的回调拿到的promise
            // return Promise.reject(1)

            //2 返回非promise的任意值,那么下个then拿到的是返回值,并且默认执行resolve
            // return 2
            // return "aaa"
            // return  不返回,则默认返回undefined

            //3 抛出异常,下个then拿到的是抛出的值或对象,执行reject
            throw 1
        },
        reason => { }
    ).then(
        value => console.log(value),
        reason => console.log(reason)
    )

Promise如何串联执行多个操作任务,不论同步异步都保证顺序执行?

通过then返回一个新的promise,然后通过链式调用执行多个同步/异步任务

对于同步任务,直接写即可,对于异步任务,写在Promise里面

promise.all是传入一个包含多个promise的promises数组,全部成功状态才算成功,更倾向于逻辑上的结果

new Promise((resolve, reject) => {
            resolve(1)
        }).then(
            value => {
                console.log(‘同步任务1‘)
                return 1
            }
        ).then(
            value => {
                console.log(value)
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log(‘异步任务2‘)
                        resolve(2)
                    }, 100)
                })
            }
        ).then(
            value => {
                console.log(value)
                console.log(‘同步任务3‘)
                return Promise.resolve(3)
            }
        )

技术图片

Promise异常传送?

不写rejected,then里面默认reason=》{throw reason} 往下抛,或者返回一个失败的promise,Promise.reject()

  • 使用then时,可以在最后指定失败的回调
  • 前面任何操作出了异常,都会在最后的失败回调中处理
    new Promise((resolve, reject) => {
        resolve(1)
    }).then(
        value => {
            console.log(‘同步任务1  ‘, value)
            throw 100;//沿着this链式,一直传到末尾的catch 
        }
        //不写相当于reason => throw  reason,下面也是一样
    ).then(
        value => {
            console.log(value)
        }
    ).then(
        value => {
            console.log(value)
            console.log(‘同步任务3‘)
            return Promise.resolve(3)
        }
    ).catch(reason => console.log(reason, ‘~~~‘))

    /* 
                9 promise q4.html:16 同步任务1   1
                9 promise q4.html:30 100 "~~~"  
             */

中断Promise链?

使用then时,在中间中断,不再调用后面的回调函数

办法:在回调函数种返回一个pending状态的promise对象 return new Promise(()=>{}), 就是让Promise没有结果,那么then也不再向下执行

    new Promise((resolve, reject) => {
        resolve(1)
    }).then(
        value => {
            console.log(‘同步任务1  ‘, value)
            return Promise.resolve(3)
        }
    ).then(
        value => {
            console.log(value)
            //返回一个不做状态转变的promise,让promise处于pending状态,即可中断
            return new Promise(()=>{})
        }
    ).then(
        value => {
            console.log(value)
            console.log(‘同步任务3‘)
            return Promise.resolve(3)
        }
    )
    /* 
                9 promise q4 中断.html:16 同步任务1   1
                9 promise q4 中断.html:21 3
    */

以上是关于初探Promise 中断与异常传送的主要内容,如果未能解决你的问题,请参考以下文章

异常和TCP通讯

VSCode自定义代码片段12——JavaScript的Promise对象

VSCode自定义代码片段12——JavaScript的Promise对象

初探 Objective-C/C++ 异常处理实现机制

Kotlin初探

Kotlin初探