zoukankan      html  css  js  c++  java
  • ES6学习总结之 Promise对象

    1.什么是Promise?

    1.定义:所谓Promise对象,是用来传递异步操作消息的对象。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。

    2.特点:

    a.对象状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中),Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其它操作都无法改变这个状态。

    b.一旦状态改变就不会再变,任何时候都是这个结果。Promise对象的状态改变只有两种可能:从Pending变为Resolved的和从Pending变为Rejected。只要其中之一发生,状态就不会再变。

    3.缺点:

    a.无法取消Promise,一旦新建就会立即执行,无法取消。

    b.如果没有设置回调函数,Promise内部抛出的错误无法在外部接收。

    c.处于Pending状态时,无法得知目前进展如何(刚开始还是即将完成)。

     2.基本用法

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

    var promise = new Promise(function(reslove,reject){
          
         if(/*异步操作成功*/){
           resolve(value); 
        }
        else{
           reject(error); 
        }
    })

    Promise构造函数接受一个函数作为参数,该函数的两个参数resolve和reject是两个函数,由js引擎提供,不需要自己设置。

    resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 Pending 变为 Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    Promise实例生成以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数:

    promise.then(function(value) {
      // 成功时
    }, function(error) {
      // 失败时
    });

    then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Rejected时调用。第二个函数是可选的。这两个函数都接受Promise对象传出的值作为参数。

    以下是一个Promise对象封装Ajax请求的例子:

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

    上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,表示异步操作的结果可能是一个值,也可能是另一个异步操作。注意下面代码:

    var p1 = new Promise(function (resolve, reject) {
      setTimeout(() => reject(new Error('fail')), 4000)
    })
    
    var p2 = new Promise(function (resolve, reject) {
      setTimeout(() => resolve(p1), 2000)
    })
    
    p2.then(result => console.log(result))
    .
    catch(error => console.log(error))

    上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作,此时p1的状态就会传递给p2。p1是一个Promise,4秒之后变为rejected。p2的状态在2秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,p2也跟着变为Rejected。

     3.Promise.prototype.then()

    Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

    采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

    getJSON("/post/1.json").then(
      post => getJSON(post.commentURL)
    ).then(
      comments => console.log("Resolved: ", comments),
      err => console.log("Rejected: ", err)
    );

    上述代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用comments => console.log("Resolved: ", comments),如果状态变为Rejected,就调用err => console.log("Rejected: ", err)。

     4.Promise.prototype.catch()

    1.Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

    getJSON('/posts.json').then(function(posts) {
      // ...
    }).catch(function(error) {
      // 处理 getJSON 和 前一个回调函数运行时发生的错误
      console.log('发生错误!', error);
    });

    2.如果Promise状态已经变成Resolved,再抛出错误是无效的。

    3.Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

    getJSON('/post/1.json').then(function(post) {
      return getJSON(post.commentURL);
    }).then(function(comments) {
      // some code
    }).catch(function(error) {
      // 处理前面三个Promise产生的错误
    });

    4.一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。

    5.跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

    6.catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

    7.catch方法之中,还能再抛出错误。

    5.Promise.all()

    Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

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

    上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

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

    (1)只有p1、p2、p3的状态都变成resolved,p的状态才会变成resolved,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

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

    // 生成一个Promise对象的数组
    var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
      return getJSON('/post/' + id + ".json");
    });
    
    Promise.all(promises).then(function (posts) {
      // ...
    }).catch(function(reason){
      // ...
    });

    上面代码中,promises是包含6个 Promise 实例的数组,只有这6个实例的状态都变成resolved,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

    6.Promise.race()

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

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

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

    Promise.race方法与Promise.all方法一样,如果参数不是 Promise 实例,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

    7.Promise.resolve()

    Promise.resolve方法可以将现有对象转为Promise对象。

    Promise.resolve等价于下面的写法。

    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))

    Promise.resolve方法的参数分成四种情况。

    1.参数是一个Promise实例

    如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

    2.参数是一个thenable对象

    thenable对象指的是具有then方法的对象,Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。

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

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

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

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

    4.不带有任何参数

    Promise.resolve方法允许调用时不带参数,直接返回一个Resolved状态的Promise对象。所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。

    var p = Promise.resolve();
    
    p.then(function () {
      // ...
    });

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

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

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

    8.Promise.reject()

    Promise.reject(value)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

    var p = Promise.reject('有错误');
    // 等同于
    var p = new Promise((resolve, reject) => reject('有错误'))
    
    p.then(null, function (s) {
      console.log(s)
    });
    // 出错了

    上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行。注意,Promise.reject()方法的参数,会原封不动地作为reject的值,变成后续方法的参数。这一点与Promise.resolve方法不一致。

    const thenable = {
      then(resolve, reject) {
        reject('出错了');
      }
    };
    
    Promise.reject(thenable)
    .catch(e => {
      console.log(e === thenable)
    })
    // true

    上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

    9.done()方法和finally()方法

    ES6的Promise API提供的方法不是很多,有些有用的方法可以自己部署,比如下面两个方法。

    1.done()方法

    Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

    Promise.prototype.done = function (onResolved, onRejected) {
      this.then(onResolved, onRejected)
        .catch(function (error) {
          // 抛出一个全局错误
          setTimeout(() => { throw error }, 0);
        });
    };

    从上面代码可见,done方法的使用,可以像then方法那样用,提供resolved和rejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。

    2.finally()方法

    finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

    server.listen(0)
      .then(function () {
        // ...
      })
      .finally(server.stop);
    
    Promise.prototype.finally = function (callback) {
      let P = this.constructor;
      return this.then(
        value  => P.resolve(callback()).then(() => value),
        error => P.resolve(callback()).then(() => { throw error })
      );
    };

    上面代码中,不管前面的Promise是resolved还是rejected,都会执行回调函数callback。

  • 相关阅读:
    「日常训练」Single-use Stones (CFR476D2D)
    「日常训练」Greedy Arkady (CFR476D2C)
    「Haskell 学习」二 类型和函数(上)
    「学习记录」《数值分析》第二章计算实习题(Python语言)
    「日常训练」Alena And The Heater (CFR466D2D)
    Dubbo 消费者
    Dubbo 暴露服务
    Rpc
    git fail to push some refs....
    Spring Cloud (6)config 客户端配置 与GitHub通信
  • 原文地址:https://www.cnblogs.com/Cathamerst/p/7446650.html
Copyright © 2011-2022 走看看