zoukankan      html  css  js  c++  java
  • Promise超级详细解读

    Promise的使用和踩坑

    (本文部分内容直接引用了阮一峰老师的《ES6入门》)

    1.Promise的基本使用

    ①什么是Promise

    Promise是异步编程的一种解决方案,他能避免回调函数的层层嵌套,带来的难以阅读和维护等问题,例如下面的回调地狱:

    setTimeout(() => {
        console.log(1)
        setTimeout(() => {
            console.log(2)
            setTimeout(() => {
                console.log(3)
                setTimeout(() => {
                    console.log(4)
                }, 3000)
            }, 3000)
        }, 3000)
    }, 3000)
    

    这里只是简单的展示了一下回调地狱,这还只是嵌套了四层,还可以读懂维护,但如果是一百层甚至更多,就变得难以维护,如果换成Promise来书写,可以这样:

    const prom = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 3000)
    })
    prom.then(() => {
        console.log(1)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, 3000)
        })
    }).then(() => {
        console.log(2)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, 3000)
        })
    }).then(() => {
        console.log(3)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, 3000)
        })
    }).then(() => {
        console.log(4)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, 3000)
        })
    })
    

    虽然代码量增加了,但是却增加了代码的可读性和可维护性,首先把每次要打印的数字和setTimeout()进行了分离,打印写在了下一个then()中,而setTimeout()写在上一个then()中。

    ②promise的基本使用

    介绍完Promise的优点,我们来说一下它的基本使用:

    // 使用new Promise来创建一个Promise实例,参数为一个函数,而传入的函数又带有两个参数分别是resolve和reject,这两个参数也是函数,是JS原生的
    new Promise((resolve, reject) => {
        // 
        // 在这里你可以放入一个异步操作,比如是一个网络请求,result是你网络请求的结果,你通过对结果的判断来决定Promise的状态是fulfilled还是rejected
        //
        if(result) {
            resolve()
        } else {
            reject()
        }
    })
    .then(() => {})
    .catch(() => {})
    

    promise对象有三种状态pending(进行中)、fulfilled(已成功)和rejected(已失败),在一开始promise始终处于pending状态,当你通过对结果的判断,去手动指定它的状态是也就是使用resolve() 或者 reject()就会使promise的状态变为fulfilled或者rejected,而且promise 的状态一旦发生改变就确定了。

    当promise的状态变为fulfilled时,就会去执行then(),当状态变为rejected,就会去执行catch()。

    new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('我是一个异步操作')
        }, 1000)
        // 在异步操作之后没有指定promise是fulfilled还是rejected,所以后面的then()和catch()都不会执行
    })
    .then(() => {
        console.log('then')
    })
    .catch(() => {
        console.log('catch')
    })
    

    promise的几个注意事项:

    then()方法可以接收两个参数。这两个参数都是函数,由你自己设定,第一个函数用来处理当promise状态变为fulfilled时,第二个函数用来处理当promise状态变为rejected时,如下

    new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('我是一个异步操作')
        }, 1000)
    })
    .then(() => {
        console.log('then')
    }, () => {
        console.log('catch')
    })
    //
    这种写法等同于上面的写法
    

    但是我们不推荐这种写法,推荐then().catch()的写法,因为采用then(() => {}, () => {})这种写法只能捕获前面promise实例内部产生的错误,但是如果是then().catch()这种写法,如果promise实例产生了错误可以捕获到,而且当then()内部产生错误的时候也可以被catch()捕获到。

    resolve()和reject()都可以传递参数,一般传递给resolve()的参数是异步操作的结果,例如一次网络请求的结果就可以当做参数传递给resolve(),而结果就可以在then()中处理,而一般传递给reject()的参数一般是一个Error对象的实例。如:

    new Promise((resolve, reject) => {
        resolve(1)
    }) // then 中的data(变量名任取)就是接收的resolve()传递过来的参数
    .then((data) => {
        console.log(data + 1) // 2
    })
    .catch(() => {
        
    })
    
    new Promise((resolve, reject) => {
        reject(new Error('test'))
    }) 
    .then(() => {
    }) // catch 中的e(变量名任取)就是接收的reject()传递过来的参数
    .catch((e) => {
        console.log(e) // Error: test
    })
    

    promise实例一被创建就会立即执行,如

    new Promise((resolve, reject) => {
        console.log('我是promise实例') // 首先打印
        resolve()
    }) 
    .then(() => {
        console.log('then被执行了') // 最后打印
    }) 
    .catch(() => {
        
    })
    console.log(1) // 第二打印
    

    then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行。所以最后才打印了'then被执行了'。

    对于resolve()的参数除了第二条我们提到的外还可以是一个promise实例

    // 先创建一个promise实例,内部传入回调函数,在3秒后状态变为rejected
    const prom1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error('test'))
        }, 3000)
    })
    // 创建另一个promise实例,内部传入回调函数,1秒后状态变为fulfilled,并传入刚才创建的prommise实例prom1
    const prom2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(prom1)
        },1000)
    })
    prom2
    .then((data) => {
        console.log(data)
    }) // 虽然prom2的状态在1秒时本该变为fulfilled,但是由于传入的参数为promise实例,所以prom2的状态会由prom1决定,又过了2秒后prom1变为了rejected,prom2状态也变为rejected
    .catch((e) => {
        console.log(e) //打印Error:test
    })
    

    可以看出传入的promise实例会影响他所在的promise实例的状态

    resolve()或者reject()后面的语句仍会执行不会就到resolve()或者reject()就终止,如:

    const prom = new Promise((resolve, reject) => {
        resolve()
        console.log('我还可以执行')
    })
    prom.then(() => {
        console.log('then被执行了')
    })
    // 控制台
    // '我还可以执行'
    // 'then被执行了'
    

    而我们一般是把resolve() 或者reject()后面的代码放到then()或者catch()中去执行,所以一般我们在resolve()或者reject()前面加上return,如:

    const prom = new Promise((resolve, reject) => {
        return resolve()
        console.log('我还可以执行') // 不会被执行了
    })
    prom.then(() => {
        console.log('then被执行了')
    })
    // 控制台
    // 'then被执行了'
    

    如果在给promise实例的状态指定为rejected或者代码中存在错误,如果没有指定.catch()那么浏览器就会报错。而在fulfilled状态下没有指定.then()却没有关系,如:

    const prom = new Promise((resolve, reject) => {
        reject(new Error('test')) // 手动设置为rejected状态
    })
    

    或者

    const prom = new Promise((resolve, reject) => {
        console.log(e) // 产生了一个引用错误,因为e未声明
    })
    

    所以我们在创建完promise实例后,最好都在后面加一个catch块儿。等下会继续在下面专门讲解catch()。

    2.promise相关的其他几个方法

    promise的实例能够调用then()、catch()等方法,是因为在promise的原型上定义了这些方法,除此之外还有finally(),另外属于Promise()函数的方法还有all()、race()、allSettled()、any()、resolve()、reject()等,下面会一一介绍他们。

    一个promise实例通过 proto 指向了原型Promise,然后可以看到原型有then()、catch()、finally(),原型的constructor指向函数Promise(),然后Promise()函数还有all()、race()、allSettled()、any()、resolve()、reject()这些方法。

    ①Promise.prototype.then()

    then()方法用来处理promise实例状态改变后的回调函数,前面说过then()接受两个参数,第一个是promise实例状态变为fulfilled时候回调的函数,第二个是promise实例状态变为rejected时候回调的函数,第二个参数可以不写。then()方法返回的也是一个promise实例因此可以使用链式写法,并且如果then()默认返回的实例是fulfilled状态,除非你手动抛出了错误或者回调函数本身有错误产生。例子如下:

    const prom = new Promise((resolve, reject) => {
        resolve()
    })
    prom
    .then(() => {
        console.log(1)
    })
    .then(() => {
        console.log(2)
    })
    .then(() => {
        console.log(3)
    })
    

    ②Promise.prototype.catch()

    catch()可以说是then()的一种特例,它等同于

    const prom = new Promise((resolve, reject) => {
        reject()
    })
    prom.catch(() => {
        console.log('catch')
    })
    // 等同于以下两种写法
    //  prom.then(undefined, () => {
    //      console.log('catch')
    //  })
    //  prom.then(null, () => {
    //      console.log('catch')
    //  })
    

    除此之外,对于promise状态手动调用reject()使其状态变为rejected,还有以下两种等价写法:

    const prom = new Promise((resolve, reject) => {
        reject(new Error('test'))
    })
    prom
    .catch((e) => {
        console.log(e)
    })
    // 等价写法1
    const prom1 = new Promise((resolve, reject) => {
        throw new Error('test')
    })
    prom1
    .catch((e) => {
        console.log(e)
    })
    // 等价写法2
    const prom2 = new Promise((resolve, reject) => {
        try {
            throw new Error('test')
        } catch (e) {
            reject(e)
        }
    })
    prom2
    .catch((e) => {
        console.log(e)
    })
    

    catch()也会返回一个promise实例并且默认状态是fulfilled。

    promise产生的错误具有“冒泡”“吃掉”的特点,冒泡就是说promise在链式写法时,某一处产生了错误,如果你后面写了catch(),那么这个错误就会一直冒泡到catch()处,被它捕获,并且你可以在后面继续调用then()。吃掉是指,如果promise内部产生了错误,那么他不会影响外部程序的执行,因为我们知道浏览器在检测到程序出错时,后面的程序就不会再执行了,但是promise内部的错误虽然会在浏览器报错但不会对外部程序产生影响,就像被它自己吃掉了。来看两个例子:

    const prom = new Promise((resolve, reject) => {
        resolve()
    })
    prom
    .then(() => {
        console.log(1)
    })
    .then(() => {
        console.log(2)
    })
    .then(() => {
        // 我们制造出了一个错误,因为e没有声明
        console.log(e)
    })
    .catch(() => {
        console.log('未声明') // 会被catch捕获
    })
    .then(() => {
        console.log(3)
    })
    

    错误被捕获到之后,又返回了一个promise实例,又接着调用了后面的then()。

    看一个promise吃掉错误的例子:

    const prom = new Promise((resolve, reject) => {
                console.log(a)
            })
            setTimeout(() => {
                console.log('没有被影响')
            }, 3000)
    

    我们产生了一个未声明就引用的的错误,并且没有设置catch()捕获错误,但是在3秒后依然打印了,表示错误没有跑到外面,而是被吃掉了。

    另外在resolve()之后再抛出错误就不会再被捕获了,因为promise翻译过来就是承诺的意思,既然前面已经把实例的状态变为fulfilled那么就不会再变为rejected的了。

    ③Promise.prototype.finally()

    finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。并且finally()的回调函数不接收任何参数,因此无法知道实例的状态。

    promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
    

    ④Promise.all()

    下面这些方法都是存在于Promise()上的,对于使用new Promise()创建的实例并不能调用这些方法,这点大家要记住。

    all()方法传入的是一组数组,数组的每一项是一个Promise的实例,如果传入的项不是Promise的实例,那么就会调用下面要介绍的方法Promise.resolve()将他转换为Promise实例,(在《es6入门》这本书中提到:Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。这句话我还不太理解,后面我弄懂了再做补充吧)。然后Promise.all()会将这个数组包装成一个新的promise实例。

    例如:

    const prom = new Promise((resolve, reject) => {
                resolve()
            })
            const p1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(1)
                }, 1000)
            })
            const p2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(2)
                }, 2000)
            })
            const p3 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(3)
                }, 3000)
            })
            Promise.all([p1, p2, p3]).then((data) => {
                console.log(data)
            })
    

    prom的状态由p1p2p3决定,分成两种情况。

    (1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

    (2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    第二条需要注意的是,如果p1,p2 ,p3中自己定义了catch(),并产生了错误,那么错误将会被他自己的catch()捕获,然后返回的是一个状态为fulfilled的promise实例,不会触发Promise.all()的catch()。

    例如:

    const prom = new Promise((resolve, reject) => {
                resolve()
            })
            const p1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(1)
                }, 1000)
            })
    
            const p2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject(new Error('我是p2的错误'))
                }, 2000)
            }).catch(() => {
                console.log('p2的catch') // 在这里给p2添加了一个catch()
            })
    
            const p3 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(3)
                }, 3000)
            })
            
            Promise.all([p1, p2, p3]).then((data) => {
                console.log(data)
            }).catch(() => {
                console.log('all的catch')
            })
    

    ⑤Promise.race()

    该方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

    const p = Promise.race([p1, p2, p3]);
    

    race就是比赛的意思,也就是p1, p2,p3中只要有一个实例的状态确定下来了,那么p的状态也就随之确定,那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

    ⑥Promise.allSettled()

    这个方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束,该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

    const resolved = Promise.resolve(42);
    const rejected = Promise.reject(-1);
    
    const allSettledPromise = Promise.allSettled([resolved, rejected]);
    
    allSettledPromise.then(function (results) {
      console.log(results);
    });
    // [
    //    { status: 'fulfilled', value: 42 },
    //    { status: 'rejected', reason: -1 }
    // ]
    

    上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个 Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

    ⑦Promise.resolve()

    promise.resolve()用来将传入的参数转换为promise对象,对于传入的参数有下面几种情况。

    (1)传入的是一个promise对象

    如果传入的是一个promise对象,那么返回的仍然是这个promise对象。

    (2)传入的是一个具有then 方法的对象

    如:

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      } // 这里的函数是可以任写的这里写这样的一个函数是为了能让下面的操作更有意义
    };
    let p1 = Promise.resolve(thenable);
    p1.then(function (value) {
      console.log(value);  // 42
    });
    

    方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。thenable对象的then()方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then()方法指定的回调函数,输出42。

    (3)参数不是具有then()方法的对象,或根本就不是对象

    如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。

    const p = Promise.resolve('Hello');
    
    p.then(function (s) {
      console.log(s)
    });
    // Hello
    

    上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve()方法的参数,会同时传给回调函数。

    (4)不带有任何参数

    Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

    所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

    需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.resolve().then(function () {
      console.log('two');
    });
    
    console.log('one');
    
    // one
    // two
    // three
    

    setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

    ⑧Promise.reject()

    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

    Promise.reject('出错了')
    .catch(e => {
      console.log(e === '出错了')
    })
    // true
    
  • 相关阅读:
    关于C51的中断函数要注意的几个问题
    WORD文档中插入页码的问题
    【转载】vim复制时的缩进
    【转载】在Linux下,一个文件也有三种时间,分别是:访问时间、修改时间、状态改动时间
    【转载】LINUX上MYSQL优化三板斧
    【转载】小结一下linux 2.6内核的四种IO调度算法
    【转载】linux挂载mount参数优化
    【转载】ipcs与Linux共享内存
    【转载】Linux下的IO监控与分析
    【转载】netstat--查看服务器[有效]连接数--统计端口并发数--access.log分析
  • 原文地址:https://www.cnblogs.com/jojo-star/p/13822172.html
Copyright © 2011-2022 走看看