美文网首页javascript
javascript中Promise

javascript中Promise

作者: YINdevelop | 来源:发表于2019-09-27 11:50 被阅读0次

    1.Promise是什么?

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

    2. 基本用法

    Promise对象是一个构造函数,用来生成Promise实例。下面我们来

    1.创建一个promise实例:

    const promise = new Promise(function(resolve, reject) {
        // ... some code
        if (/* 异步操作成功 */){
            resolve(value);
        } else {
            reject(error);
        }
    });
    
    • Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
    • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    2.使用promise实例

    Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数,从而接收传递过来的状态。

    promise.then(function(value) {
        // success
    }, function(error) {
        // failure
    });
    
    • then方法可以接受两个回调函数作为参数。
    • 第一个回调函数是Promise对象的状态变为fulfilled时调用
    • 第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供
    • 这两个函数都接受Promise对象传出的值作为参数。

    3. 特点

    从上面的用法看,我们可以了解到Promise有如下特点:

    1. Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有调用了resolve/reject函数来处理结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    举个例子:

    var get = new Promise(function (resolve, reject) {
        console.log(1);
        resolve(2)
        console.log(3)
        setTimeout(function(){
            console.log(5)
        },1);
    })
    get.then(function (value) {
        console.log(value);
    })
    console.log(4);
    
    //输出结果为:1 3 4 2 5
    

    从上面例子我们可以看出,只要使用new Promise,相当于创建一个异步操作。只有调用resolve或者reject才能触发异步操作。所以我们一般在promise内放置异步操作(如请求等),当返回结果,调用resolve或者reject。这里为了简洁明了说明其是异步,所以没有放异步操作,直接调用了resolve函数,从打印结果就可以看出其是异步操作。

    1. Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    我们更改上面例子:

    var get = new Promise(function (resolve, reject) {
        console.log(1);
        resolve(2)
        reject(5)
        console.log(3)
    })
    get.then(function (value) {
        console.log(value);
    },function(error){
        console.log(error);
    })
    console.log(4);
    
    //输出结果仍为:1 3 4 2
    

    从例子结果可以看出,promise对象要么是fulfilled,要么是rejected,所以不能同时输出

    1. 一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

      new Promise((resolve, reject) => {
      return resolve(1);
      // 后面的语句不会执行
      console.log(2);
      })

    下面是一个用Promise对象实现的 Ajax 操作的例子。

    const getJSON = function(url) {
        const promise = new Promise(function(resolve, reject){
            const handler = function() {
                if (this.readyState !== 4) {
                    return;
                }
                if (this.status === 200) {
                    resolve(this.response);
                } else {
                    reject(new Error(this.statusText));
                }
            };
            const client = new XMLHttpRequest();
            client.open("GET", url);
            client.onreadystatechange = handler;
            client.responseType = "json";
            client.setRequestHeader("Accept", "application/json");
            client.send();
    
        });
        return promise;
    };
    
    getJSON("/posts.json").then(function(json) {
        console.log('Contents: ' + json);
    }, function(error) {
        console.error('出错了', error);
    });
    

    4. Promise.prototype.then()

    Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态(已定型,指的是fulfilled状态))的回调函数,第二个参数(可选)是rejected状态的回调函数。

    上面我们讲过 回调地域问题,现在我们用promise进行链式调用来解决该问题

    const request = url => { 
        return new Promise((resolve, reject) => {
            $.get(url, data => {
                resolve(data)
            });
        })
    };
    
    request(url).then(data1 => {
        return request(data1.url);   
    }).then(data2 => {
        return request(data2.url);
    }).then(data3 => {
        console.log(data3);
    })
    

    从代码上看,是不是更直观更简洁。

    5. Promise.prototype.catch

    Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。我们分别讲解then函数和catch函数,对比两者的区别:

    5.1 then函数的第二个参数:错误回调rejection

    1.当调用reject函数时候,执行rejection回调

    var get = new Promise(function (resolve, reject) {
        reject(a)
    })
    get.then(null, function (error) {
        console.log(error); //1
    })
    

    运行结果如下:

    20190925150628.png

    2.当程序运行错误,执行rejection回调

    var get = new Promise(function (resolve, reject) {
          console.log(a);
    })
    get.then(null, function (error) {
        console.log(error); 
    })
    

    运行结果和上面一样:


    20190925150628.png

    由上面2个例子我们可以知道,reject等同于抛出错误。供后面回调函数捕获错误。

    2.Promise 内部的错误不会影响到 Promise 外部的代码运行,通俗的说法就是“Promise 会吃掉错误”(js不会因为错误崩溃导致下面代码无法运行)。

    var get = new Promise(function (resolve, reject) {
        reject(a)
    })
    console.log(1);
    setTimeout(function(){
        console.log(2)
    },1);
    

    运行结果如下:

    20190925144458.png

    从结果可以看出,我们并没有使用回调函数来捕获错误,所以浏览器会报错,但是并没有终止脚本运行。

    3.Promise 的状态一旦改变,就永久保持该状态,不会再变了。所以在resolve语句后面,再抛出错误,不会被捕获

    var get = new Promise(function (resolve, reject) {
        resolve('ok')
        console.log(a)
        console.log(3)
    })
    get.then(null,function(error){
        console.log(error); //没有执行
    })
    console.log(1);
    setTimeout(function(){
        console.log(2)
    },1);
    
    // 1 2
    

    从上面例子可以看出,在resolve('ok')后面调用未定义变量a抛出错误,回调函数并没有执行捕获。在这有个奇怪的问题,上面我们在讲解promise特点时候,举过例子证明resolve后面语句也会执行,这里为什么打印结果不是 3 1 2?

    这里需要注意的是:由于resolve后面抛出错误,所以后面语句会被阻止运行(同步异步都会被阻止)。如果没有抛出错误,则正常运行。

    5.2 catch函数

    上面讲了then函数的第二个参数:我们知道了如果在promise中如果有错,可以使用错误回调rejection进行捕获。那catch呢?和rejection一样,也一样可以捕获。

    var get = new Promise(function (resolve, reject) {
        reject(a)
    })
    get.catch(function (error) {
        console.log(error); //1
    })
    

    运行结果如下:

    20190925150628.png

    那么他们两个到底有什么区别呢?唯一的区别就是,如果在 then 的第一个成功回调函数里抛出了异常,catch 能捕获到,而错误回调函数捕获不到

    var get = new Promise(function (resolve, reject) {
        resolve('ok')
    })
    get.then(function (value) {
        console.log(a);
        console.log(value); //因报错被阻止运行
    },function(error){
        console.log(error); //没有执行捕获,所以浏览器报错
    })
    

    所以运行结果如下:

    20190925151125.png

    现在我们改成catch就可以捕获

    var get = new Promise(function (resolve, reject) {
        resolve('ok')
    })
    get.then(function (value) {
        console.log(a);
        console.log(value); //因报错被阻止运行
    }).catch(function(error){
        console.log(error); //可以捕获
    })
    

    运行结果如下:

    20190925151248.png

    所以一般总是建议,不用rejection回调函数来捕获错误,用catch方法,这样既可以处理 Promise 内部发生的错误,又可以处理成功回调函数中的错误。

    有的人可能会好奇,那假若都有错误怎么办?

    var get = new Promise(function (resolve, reject) {
      console.log(e);
      resolve('ok')  //报错所以直接进入捕获函数,因为只可能有一种状态
      console.log(1); //因报错被阻止运行
    })
    get.then(function (value) {
      console.log(value); //不会调用
    }).catch(function(error){
        console.log(error);
    })
    

    运行结果如下:


    20190925151924.png

    Promise对象的状态改变,只可能有一种结果,要么成功,要么失败,不能改变,因为上面直接报错,所以直接进入catch函数中。这里只以catch举例,实际此时用catch或者rejection回调函数都可以。

    注意:如果同时用rejection回调函数和catch函数捕获错误,只会优先执行rejection回调函数,不会执行catch函数。

    6. Promise.prototype.finally

    finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的

    var get = new Promise(function (resolve, reject) {
      resolve(1)  //报错所以直接进入捕获函数,因为只可能有一种状态
    })
    get.then(function (value) {
      console.log(value); //不会调用
    }).catch(function(error){
        console.log(error);
    }).finally(function(){
        console.log(2);
    })
    console.log(3);
    setTimeout(() => {
        console.log(4);
    }, 0);
    
    // 3 1 2 4
    

    这里只举了成功状态,失败状态一样,就不举例了。

    7. Promise.resolve(value)

    该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。

    如果传入不同类型的 value 值,返回结果也有区别:

    1. value本身就是Promise对象,返回结果和入参value相同
    let get = new Promise(function (resolve, reject) {
        resolve(1) 
    })
    let p1= Promise.resolve(get)
    console.log(p1===get); //true
    
    1. value是个thenable对象(即该对象有属性名字为then方法),返回结果为Promise对象,其跟随 thenable 对象中的then函数状态(resolved/rejected)
    //demo1
    let thenable = {
        then: function (resolve, reject) {
            resolve(1);
        }
    };
    let p1 = Promise.resolve(thenable)
    p1.then(function(value){
        console.log(value); //1
    })
    
    //demo2
    let thenable = {
        then: function (resolve, reject) {
            reject(2);
        }
    };
    let p1 = Promise.resolve(thenable)
    p1.then(null,function(error){
        console.log(error) //2
    })
    

    我们平常用的jquery的ajax就是thenable对象,我们打印看下:

     console.log($.ajax());
    

    打印结果如下图:

    20190925175951.png
    1. value不是具有then方法的对象,或根本就不是对象如字符串、数值等,返回结果为一个resolved状态的 Promise 对象
    const p = Promise.resolve('Hello');
    p.then(function (s){
        console.log(s)
    });
    console.log(p)
    

    打印结果如下图:

    20190926110002.png

    所以,假如我们用错误回调函数接收,是不会执行的。

    const p = Promise.resolve('Hello');
    p.then(null,function (error){
        console.log(error)
    });
    console.log(p);
    

    打印结果如下图:

    20190926110359.png
    1. value不传,返回结果为一个resolved状态的 Promise 对象
     const p = Promise.resolve();
     p.then(function (s){
         console.log(s)
     });
     console.log(p);
    

    打印结果如下图:

    20190926111545.png

    8. Promise.reject(value)

    该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。

    如果传入不同类型的 value 值,返回结果都为一个rejected状态的 Promise实例(和上面讲的resolve方法不一样)。所以调用该实例会直接调用catch方法或者then第二个参数,其值就是传进去的value值

    1. value是promise对象
    let get = new Promise(function (resolve, reject) {
        resolve(1) 
    })
    let p1= Promise.reject(get)
    p1.catch(function(value){
        console.log(value===get); //true
    })
    

    注意,如果value为promise对象,该对象里面不能用reject或者语法错误,具体原因暂不清楚。

    let get = new Promise(function (resolve, reject) {
        reject(1) 
    })
    let p1= Promise.reject(get)
    p1.catch(function(value){
        console.log(value===get);
    })
    
    20190926162816.png
    1. value是个thenable对象
    let thenable = {
        then: function (resolve, reject) {
            resolve(1);
        }
    };
    let p1 = Promise.resolve(thenable)
    p1.catch(function(value){
        console.log(value===thenable); //true
    })
    
    1. value不是具有then方法的对象,或根本就不是对象如字符串、数值等
    const p = Promise.reject('Hello');
    p.catch(function (s){
        console.log(s)
    }); //Hello
    
    1. value不传
    let p1 = Promise.reject()
    p1.catch(function(value){
        console.log(value);
    })
    console.log(1) 
    //1 undefined
    

    注意,如果不传并且不catch捕获,会报错。比如下面代码:

     let p1 = Promise.reject()
     console.log(1) 
    

    运行结果如下:

    20190926163107.png

    9. Promise.all(arr)

    Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:

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

    上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

    p的状态由p1、p2、p3决定,分成两种情况。

    • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的then回调函数。
    • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的错误回调函数。

    10. Promise.race(arr)

    Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:

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

    上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

    和all方法不同的是:

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

    参考来源:

    《ES6标准入门——阮一峰》
    面试精选之Promise

    相关文章

      网友评论

        本文标题:javascript中Promise

        本文链接:https://www.haomeiwen.com/subject/qagouctx.html