• Promise 的含义

      • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
      • 所谓promise ,简单来说就是一个容器,里面保存着未来才会结束的事件(通常是一个异步的操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
      • Promise 对象 有两个特点:
        1. 对象的状态不受外界影响。Promise 对象代表着一个异步操作,有三种状态:pending(等待态)、fulfilled(已完成、rejected(以失败)。只有异步操作结果可以决定是哪一种状态。任何其他操作都无法改变这个状态,这也是promise (承诺)的由来。
        2. promise 的状态一旦改变就不会再变,任何时候都可以得到这个结果。promise 的状态的改变,只有两种情况:①从pending => fulfilled 或 ②从pending => rejected。
      • 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
      • Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
    • 基本用法

      • ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
      • 下面代码创造了一个Promise实例。
        • const promsime = new Prosime(function(resolve, reject){
            // todo ... 做一些事情
            if (/* 如果异步操作成功 */){
              resolve(data);
            } else {
              reject(error);
            }
          });
      • Promise构造函数接受一个函数 executor,这个函数接受两个参数 一个 resolve 和 reject。这两个参数由javaScript提供,不用自己部署。
      • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
        • promsime.then(data => { // resolved
            // sunccess
          }, err => { // rejected
            // failure
          })
      • then 方法接受两个回调函数作为参数, 一个是成功的时候回调, 一个是失败的时候回调. 第二个参数是可选的.
      • eg:一个promise的简单例子
        • function timeout(ms) {
            return new Promise((resolve, reject) => {
              setTimeout(resolve, ms, 'done');
            });
          }

          timeout(100).then((value) => {
            console.log(value);
          });
      • promise 新建后会立即执行
        • let promise = new Promise((resolve, reject) => {
              console.log('Promise');
              resolve();
          });
          
          promise.then(function() {
              console.log('resolved.');
          });
          
          console.log('Hi!');

          // Promise
          // Hi!
          // resolved
      • 下面是异步加载图片的例子。
        • function loadImageAsync(url) {
            return new Promise(function(resolve, reject) {
              const image = new Image();
          
              image.onload = function() {
                resolve(image);
              };
          
              image.onerror = function() {
                reject(new Error('Could not load image at ' + url));
              };
          
              image.src = url;
            });
          }

          上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

      • 下面是一个用Promise对象实现的 Ajax 操作的例子。
        • const getJson = (url) => {
              return 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();
              })
          }
          getJson("./posts.json").then((json) => {
              console.log('json :', json)
          }, err => {
              console.log('err :', err);
          })
      • 上面的代码中,getJson 是对XMLHttpRequest做了一个封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回了一个promise对象.需要注意的是在resolve 函数 和  reject 函数调用时都带有参.

      • 如果调用 resolve 和 reject 函数时带有参数,那么他们的参数会被传递到回调函数.reject 函数的参数一般是Error对象的实例,表示抛出的错误.resolve 函数的参数除了传递正常的值之外,还可能是一个Promise对象,像下面的例子:
        • const p1 = new Promise((resolve, reject) => {
              // todo
          });
          
          const p2 = new Promise((resolve, reject) => {
              resolve(p1);
          });

          上面代码中,p1p2都是 Promise 的实例,但是p2resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。

      • 这里需要注意的是  p1 和p2 都是Promise 的实例, 这时候 p1作为p2  resolve 的参数,就是说 p1 的状态决定 p2 的状态.如果p1 的结果是 pending , 那么p2 就会等待p1 的状态改变而改变,若p1 是 resolved  或者 rejected 中任意一种,那么p2 的回调函数就会立即执行.
      • const p1 = new Promise((resolve, reject) => {
            // todo
            setTimeout(() => reject(new Error('p1抛出的错误信息')), 3000);
        });
        
        const p2 = new Promise((resolve, reject) => {
            setTimeout(() => resolve(p1), 1000);
        });
        
        p2.then((result) => {
            console.log('result :', result);
        }).catch((err) => {
            console.log('err :', err);
        });

        上述代码中,在p1中写了个定时器 3s后执行抛出异常.p2是在1s 后执行resolve 返回的是p1.由于p2返回的是一个Promise,导致自己的状态无效了 ,等待p1的状态而确定p2的状态,所以后面的then() 语句是针对p1 的.又过了两秒p1执行reject() 抛出一个错误,状态变成rejected,导致出发catch方法指定的回调函数.

      • 注意 调用resolve 和 reject 并不会终结Promise的参数函数的执行.
        • new Promise((resolve, reject) => {
            resolve(1);
            console.log(2);
          }).then(r => {
            console.log(r);
          });
          
          // 2
          // 1
      • 上述代码中,执行resolve(1) 后 console.log(2) 还是回执行,并且先打印出来.因为promise 里面的resolve 是在本轮事件循环尾执行,总是晚于本轮循环的同步任务.
      • 一般来说调用 resolve 或者 reject 后 Promise 的使命就完成了,后续的操作我们应该放在then()方法里面,而不应该写在resolve / reject 后面.所以最好在他们前面添加 return 语句,这样既不会有意外了
        • new Promise((resolve, reject) => {
            return resolve(1);
            // 后面的语句不会执行
            console.log(2);
          })

           

  • Promise.prototype.then()

    • Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的.他的作用是为了Promise实例添加状态改变时的回调函数.
    • then 方法返回的是一个新的Promise 实例(注意: 不是原来的Promise). 因为可以采取链式写法,即then方法后面在调用另一个then 方法.
      •   eg:
        getJSON("/posts.json")
        .then(function(json) { return '123'; })
        .then(
        function(post) { console.log(post); }); // 123

         

    • 上述代码中 若是 return 的是一个异步操作,则第二个then 会等待return 的异步的返回的结果.给个栗子:
      • getJSON("/post/1.json").then(function(post) {
          return getJSON(post.commentURL);
        }).then(function funcA(comments) {
          console.log("resolved: ", comments);
        }, function funcB(err){
          console.log("rejected: ", err);
        });

         

  • Promise.prototype.catch()

    • Promise.prototype.catch() 是 .then(null,rejection)的别名,用于指定发生错误时候的回调函数.

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

      上述代码中,getJSON方法返回一个 Promise 对象,如果该对象状态为resolved,则调用then方法指定的函数,如果异步操作操作抛出错误,状态为sejected,则会调用catch方法指定的回调函数,处理这个错误。另外,then 方法指定的函数中假若抛出错误也会被catch函数扑捉。

    • eg:
      const promise = new Promise(function(resolve, reject) {
        throw new Error('test');
      });
      promise.catch(function(error) {
        console.log(error);
      });
      // Error: test

      等价于另外两种写法

      // 写法一
      const promise = new Promise(function(resolve, reject) {
        try {
          throw new Error('test');
        } catch(e) {
          reject(e);
        }
      });
      promise.catch(function(error) {
        console.log(error);
      });
      
      // 写法二
      const promise = new Promise(function(resolve, reject) {
        reject(new Error('test'));
      });
      promise.catch(function(error) {
        console.log(error);
      });

       

    • 如果 Promise 状态已经变成resolved,再抛出错误是无效的。因为Promise 的状态一旦确定了是无法改变的。
    • Promise 对象的错误具有"冒泡"性质,也就是说会一层一层往外传,错误总会被下一个catch所扑捉。eg:
      getJson('./file1.json')
        .then((data) => {
          getJson(data.fileUrl);
        })
        .then((data) => {
          // todu
        })
        .catch((err) => {
          console.log('err :', err);
        });

      上述代码中 一共有三个 Promise , 一个是getJson 产生的 两个是 then 产生的 它们三个中任意一个抛出的错误异常都会被catch扑捉。

    • 之前在then方法里写 两个函数,一个是resolve的回调函数,另一个是 reject 的回调函数。但是通常我们不建议这样写,不在then方法里面写 reject 的回调,我们一般是用catch 方法来捕获错误信息。这样写可以then 方法里面执行的报错。这样写也更接近同步的写法(try/catch)。跟传统的 try/catch 不同的是,Promise 如果没有catch指定处理错误信息的回调函数,promise 抛出的错误是不会传递到外层代码,即不会有任何反应。
  • 应用

冒泡

posted on 2018-12-02 14:50  xzqyun  阅读(239)  评论(0编辑  收藏  举报