js-Promise

栏目: JavaScript · 发布时间: 5年前

内容简介:Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 Promises/A , Promises/A +等规范,用于解决回调金字塔问题。什么是Promise?一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。Promise表示一个异步操作的最终结果。

一、为什么需要Promise

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 Promises/A , Promises/A +等规范,用于解决回调金字塔问题。

// 回调金字塔
    asyncOperation((data) => {
        // 处理data
        anotherAsync((data1) => {
            // 处理data1
            yetAnotherAsync(() => {
                // 处理完成
            });
        });
    });
    
    // 引入 Promise 之后
    promiseSomething()
        .then((data) => {
            // 处理data
            return anotherAsync();
        })
        .then((data1) => {
            // 处理data1
            return yetAnotherAsync();
        })
        .then(() => {
            // 完成
        });

什么是Promise?

一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。Promise表示一个异步操作的最终结果。

二、Promise/A+基本的规范

  1. 一个Promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)。
  2. 一个Promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换。
  3. Promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个Promise,同一个Promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致。
  4. then方法接受两个参数,第一个参数是成功时的回调,在Promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在Promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个Promise传入,也接受一个“类then”的对象或方法,即thenable对象。ajax就是一个thenable对象。

Promise状态变化

js-Promise

优点:

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

//不友好的层层嵌套
loadImg('a.jpg', function() {  
    loadImg('b.jpg', function() {  
        loadImg('c.jpg', function() {  
            console.log('all done!');  
        });  
    });  
});

缺点:

Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

三、ES6 Promise基本的API

  1. Promise.resolve() // 生成一个成功的promise对象
  2. Promise.reject() // 生成错误的一个promise对象
  3. Promise.prototype.then() // 核心部分

    • 返回一个新的Promise。
  4. Promise.prototype.catch() // 异常捕获
  5. Promise.all()

    • 接收 promise对象组成的数组作为参数(promise.all方法的参数可以不是数组,但必须具有Iterator接口)。
    • 当这个数组里的所有promise对象 全部变为resolve或遇到第一个reject状态的时候,它才会去调用 .then 方法。
    • 传递给 Promise.all 的promise并不是一个个的顺序执行的,而是 同时开始、并行执行的。
  6. Promise.race() // 最先执行的promise结果

    • 只要有一个Promise对象进入 resolve 或者 reject 状态的话,就会调用后面的.then方法。
    • 如果有一个Promise对象执行完成了,后面的还会不会再继续执行了呢? 在 ES6 Promises 规范中,也没有取消(中断)Promise对象执行的概 念,我们必须要确保Promise最终进入resolve or reject状态之一。所以,后面的Promise对象还是会继续执行的。

四、ES6 Promise基本用法

  1. 创建Promise对象。

    • new Promise(fn) 返回一个Promise对象
    • 在 fn 中指定异步等处理。
    • 处理结果正常的话,调用 resolve(处理结果值)。
    • 处理结果错误的话,调用 reject(Error对象)。
    //示例
    function getURL(URL) {
        return new Promise(function (resolve, reject) {
            var req = new XMLHttpRequest(); 
            req.open('GET', URL, true);           
            req.onload = function () {
                if (req.status === 200) {
                    resolve(req.responseText);
                } else {
                    reject(new Error(req.statusText));
                }
            };
            req.onerror = function () { 
                reject(new Error(req.statusText));
            };
            req.send();
        });
    }
    // 运行示例
    var URL = "http://baidu.com";    
    getURL(URL)
    .then(function onFulfilled(value){
        console.log(value); 
    })
    .catch(function onRejected(error){
        console.error(error);
    });
    // 其实 .catch 只是 Promise.then(undefined, onRejected) 的别名而已,
    // 如下代码也可以完 成同样的功能。
    getURL(URL).then(onFulfilled, onRejected);
    • 总结:

      用 new Promise 方法创建promise对象

      用 .then 或 .catch 添加promise对象的处理函数

  2. Promise.prototype.then()

    • 它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
    • then方法返回的是一个新的Promise实例。因此可以采用链式写法,即then方法后面再调用另一个then方法。
  3. Promise.prototype.catch()

    • Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
    getAjax('url/info').then(function(data) {
        // ...
    }).catch(function(error) {
        // 处理 ajax 和 前一个回调函数运行时发生的错误
        console.log('发生错误!', error);
    });
    • 总结:
      1.上面代码中,getAjax方法返回一个 Promise 对象,如果该对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
      2.Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
    • 有了then里面的第二个onRejected函数捕获错误,为什么还需要catch?

      function throwError(value) { // 抛出异常
          throw new Error(value);
      }
      // <1> onRejected不会被调用
      function main1(onRejected) {
          return Promise.resolve(1).then(throwError, onRejected);
      }
      // <2> 有异常发生时onRejected会被调用
      function main2(onRejected) {
          return Promise.resolve(1).then(throwError).catch(onRejected);
      }
      // 执行main函数
      main1(function(){
          console.log("错误异常");
      }
      // 执行main2函数
      main2(function(){
          console.log("错误异常");
      }
      /*Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
      一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。
      */
      Promise.resolve(1).then(throwError).then(null, onRejected);

      在函数main1因为虽然我们在的第二个参数中指定了用来错误处理的函数,但实际上它却不能捕获第一个参数指定的函数(本例为throwError)里面出现的错误。

      与此相对的是main2中的代码则遵循了 throwError → onRejected 的调用流程。这时候出现异常的话,在会被方法链中的下一个方法,即 .catch 所捕获,进行相应的错误处理。

    • 总结:
      .then 方法中的onRejected参数所指定的回调函数,实际上针对的是其Promise对象或者之前的Promise对象,而不是针对方法里面指定的第一个参数,即onFulfilled所指向的对象,这也是then和 catch表现不同的原因。
  4. Promise.resolve()

    有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

    该函数的参数四种情况:

    (1)参数是一个Promise实例,那么Promise.resolve将不做任何操作,原封不动的将实例返回。

    (2)参数是一个thenable对象,会将其转为Promise对象,然后立即执行该对象的then方法。

    (3)参数不是具有then方法的对象,或根本就不是对象。比如说字符之类,则Promise.resolve方法返回一个新的Promise对象,并且状态Resolved。

    (4)不带有任何参数,直接返回一个状态为Resolved的Promise对象。

    • 使用Promise.resolve()创建Promise对象

      // 静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
      // 比如 
      Promise.resolve(1)
      .then(function(value){
          console.log(value);
      });  
      // 可以认为是以下代码的语法糖。
      new Promise(function(resolve){ 
          resolve(1);
      })
      .then(function(value){
          console.log(value);
      });
      // 控制台输出1
      注意:无论Promise.resolve的参数是什么,只要变成了rejected,或者resolved。都会执行then里面的resolve函数。
    • 将 thenable 对象转换为promise对象。 什么是thenable对象?

      简单来说它就是一个非常类似promise的东西。thenable指的是一个

      具有 .then 方法的对象。jQuery.ajax(),这个对象具有 .then 方法。

      let thenable = {
          then: function(resolve, reject) {
              resolve(42);
          }
      };
      let p1 = Promise.resolve(thenable);
      p1.then(function(value) {
          console.log(value);         // 42
      });
  5. Promise.reject()

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

    Promise.reject('这是错误的信息').then(function(){
    
    },function(res){
        console.log(res);          //这里是错误信息  
    });
    // 注意:无论Promise.reject的参数是什么,只要变成了rejected,或者resolved。都会执行then里面的reject函数。
  6. Promise.all()

    Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。如果传入的不是不是Promise对象就会调用Promise.reslove()方法将其转换成Promise实例。

    const p1 = Promise.resolve(3);
    const p2 = Promise.reject(5);
    Promise.all([true, p1, p2]).then((value) => {
        console.log('成功了' + value);
    }, (value) => {
        console.log('失败了' + value);
    });
    
    // 错误了,5   
    // 如果是全部resolved,返回的是一个数组[true,3,5]
  7. Promise.race()

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

    const p1 = new Promise((resolve, reject) => {
        setTimeout(resolve, 50, 'one');
        setTimeout(() => {
            console.log('one');
            resolve('one');
        }, 4)
    });
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('two');
            resolve('two');
        }, 10)
    });
    Promise.race([p1, p2]).then((value) => {
        console.log(value);
    });
    
    // one one two 所以后面的promise对象肯定会继续执行 
    // 第一个one是在p1里面打印出来的

五、Promise只能进行异步操作?

Promise在规范上规定Promise只能使用异步调用方式。

// 可以看出promise是 一个异步函数  
var promise = new Promise(function(resolve) {
console.log("inner promise");                // 1 
    resolve(42);
});
promise.then(function(value) {
    console.log(value);                      // 3 
});
console.log("outer promise");                // 2

why?

因为同步调用和异步调用同时存在容易导致一些混乱。举个类似的例子。

function onReady(fn) {
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
        fn();
    } else {
        window.addEventListener('DOMContentLoaded', fn); 
    }
}
onReady(function () {
    console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');

如上js函数会根据执行时DOM是否已经装载完毕来决定是对回调函数进行同步调用还是异步调用。因此,如果这段代码在源文件中出现的位置不同,在控制台上打印的log消息顺序也会不同。 为了解决这个问题,我们可以选择统一使用异步调用的方式。

function onReadyPromise() {
    return new Promise(function (resolve, reject) {
        var readyState = document.readyState;
        if (readyState === 'interactive' || readyState === 'complete') {
            resolve(); 
        } else {
            window.addEventListener('DOMContentLoaded', resolve); 
        }
    }); 
}
onReadyPromise().then(function () { 
    console.log('DOM fully loaded and parsed');
}); 
console.log('==Starting==');

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

SEO深度解析

SEO深度解析

痞子瑞 / 电子工业出版社 / 2014-3-1 / CNY 99.00

《SEO深度解析》以SEO从业人员普遍存在的疑问、经常讨论的问题、容易被忽视的细节以及常见的错误理论为基础,对SEO行业所包含的各方面内容进行了深入的讨论,使读者更加清晰地了解SEO及操作思路。内容分为两类:一类为作者根据自己真实、丰富的SEO经验对SEO所涉及的各种问题进行详细的讨论,主要包括SEO 基础原理剖析、SEO实操思路方法、常用工具数据剖析、竞争对手分析案例实操、网站数据分析思路指导、......一起来看看 《SEO深度解析》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换