zoukankan      html  css  js  c++  java
  • JS: Promise

    Promise

    Promise是 JavaScript 异步编程中的重要概念,是目前较为流行的 JavaScript 异步编程解决方案之一。它最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

    Promise 的基本概念


    一个 Promise有以下几种状态:

    • pending: 初始状态,既不是成功,也不是失败状态(也可以说是进行中)。
    • fulfilled: 意味着操作成功完成。
    • rejected: 意味着操作失败。

    pending 状态的 Promise 对象可能触发 fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为 fulfilled 时,调用 then 的 onfulfilled 方法,当 Promise 状态为 rejected 时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。 ------MDN

    因为Promise.prototype.thenPromise.prototype.catch 方法会返回 promise 对象,所以它们可以被链式调用

    Promise

    Promise 的基本用法


    // 这是 Promise 的常用方法
    function promise(...) {
    	return new Promise((resolve, reject) => {
    		// do something
            if (/* fulfilled (异步操作成功) */) {
                return resolve(value);
            } 
    	reject(error);
    	});
    }
    
    promise(...).then((value) => {
        // success
    }, (error) => {
        // failure
    });
    

    Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供。

    当状态从 pending变为fulfilled时,即异步操作成功,这时调用resolve,并将异步操作的结果,作为参数传递出去。当状态从 pending变为rejected时,即异步操作失败,这时调用reject,并将error作为参数传递出去。

    Promise实例生成后,通过then方法部署fulfilled状态和rejected状态的回调函数。

    下面是一个用Promise封装 Ajax 的例子:

    const getJSON = function({
        // 解构赋值设置默认参数
        url = 'url', 
        method = 'GET', 
        async = false, 
        data = null
    	} = {}) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open(url, method, async);
            xhr.responseType = 'json';
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.onreadystatechange = function() {
                if (this.readyState == 4) {
                    let status = this.status;
                    if (status >= 200 && status < 300 || status == 304) {
                        resolve(this.response);
                    } else {
                        reject(this.statusText);
    				}
                }
            }
            xhr.send(data);
        });
    }
    
    getJSON({url: '/json'})
    	.then(json => console.log(json), error => console.log(error));
    

    Promise 的链式调用


    以上面封装的 Ajax 为例子:

    getJSON({url: '/json'})
    .then( json => getJSON({url: json.oneUrl}) )
    .then( json => getJSON({url: json.twoUrl}) )
    .catch( error => {...} )
    .then( () => {...} );
    

    因为then或者catch方法会返回一个Promise对象,或者自己return一个Promise对象,而这样就可以继续使用then或者catch方法。

    Promise.prototype.catch()


    这里的catch()try/cath的方法类似,都是捕捉错误进行处理。

    // 这两种写法是等价的
    // 第一种
    getJSON({url: '/json'}).then( (json) => {
    	// success
    }).catch( (error) => {
    	// failure
    });
    
    // 第二种
    getJSON({url: '/json'}).then((json) => {
        // success
    },(error) => {
        // failure
    });
    

    但比较建议使用第一种写法,因为使用catch让代码更接近于同步代码,而且在链式调用中,使用catch方法可以捕捉前面所有的错误进行一并处理。

    Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,而且catch还会返回一个Promise对象,这样可以继续使用then方法进行链式调用。

    getJSON({url: '/json'})
    .then( json => getJSON({url: json.oneUrl}) )
    .then( json => getJSON({url: json.twoUrl}) )
    .then( json => {...} )
    .catch( error => {
        // 这里可以处理前面所有 Promise 的错误
    }).then( () => {...} );
    

    Promise.prototype.finally()


    finally里面的操作不管 Promise 对象最后状态如何,都是会去执行的,这和 JAVA 的 finally 有点相似,即无论如何,一定执行。

    promise(...)
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {
    	// 这里的操作一定执行
    });
    

    值得注意的是,finally方法的回调函数是不接受任何参数的,这意味着没有办法知道前面的 Promise 状态到底是fulfilled还是rejected。所以finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

    Promise.resolve()


    Promise.resolve(value)方法返回一个以给定值解析后的promise对象。

    但如果这个值是个 thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);

    如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;

    否则以该值为成功状态返回promise对象。

    • 传入参数是一个promise对象

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

      const p0 = new Promise((resolve, reject) => {
      	resolve(1);
      });
      const p1 = Promise.resolve(p0);
      console.log(p1 === p0); // true
      
    • 传入参数是thenable对象

      Promise.resolve()会将这个对象转为 Promise 对象,然后立即执行thenable对象的then方法,执行完毕之后,Promise 对象状态变为fulfilled状态,然后就会执行后续的then方法。

      // thenable 即带有 then 方法的对象
      let thenable = {
          then: (resolve, reject) => {
              resolve(1);
          }
      };
      const p2 = Promise.resolve(thenable);
      p2.then( value => console.log(value) );
      
    • 传入参数为其他值

      如果参数不为前面的两种的情况,则Promise.resolve方法会以fulfilled状态返回一个新的 Promise 对象,并将传入的参数传给then方法。

      const p3 = Promise.resolve(123);
      p3.then( value => console.log(value) ); // 123
      

    Promise.reject()


    Promise.reject(value)会以rejected状态返回一个 Promose 实例,并将传入参数原封不动地传给then或者catch方法。

    const p4 = Promise.reject('错误');
    // then()
    p4.then(null, (err)=>{
            console.log(err); // 错误
        });
    // catch()
    p4.catch( err => console.log(err) ); // 错误
    

    Promise.all()


    Promise.all(iterable) 方法返回一个 Promise实例,此实例在 iterable 参数内所有的 promise 状态都为fulfilled状态(即成功)或参数中不包含 promise 时回调完成;如果参数中 promise 有一个状态为rejected状态(即失败)时回调失败,失败的原因是第一个失败 promise 的结果。

    一般来说,Promise.all方法会传入一个数组参数,如果数组成员里有非Promise实例时,会自动调用上面所说的Promise.resolve方法将其转为Promise实例,再进行处理。

    然后处理结果分两种情况:

    1.参数内所有Promise实例的状态都为fulfilled状态,则返回一个包含所有resolve结果的数组传给后续的回调函数。

    2.参数内有一个Promise实例的状态为rejected状态,则将第一个被rejectPromise实例的结果传给后续的回调函数。

    const p5 = Promise.resolve(123);
    const p6 = 42;
    const p7 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'foo');
    });
    
    Promise.all([p5, p6, p7]).then((values) => {
        console.log(values); // [123, 42, "foo"]
    });
    

    Promise.race()


    Promise.race方法与上面的Promise.all()类似,但不同的是,Promise.race()是只要传入的Promise实例中有一个状态改变了,就会直接返回这个最快改变状态的Promise实例的结果,而不是返回所有。

    const p8 = Promise.resolve(123);
    const p9 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'foo');
    });
    
    Promise.race([p8, p9]).then((values) => {
        // 这里只会获得 p8 的返回值
        console.log(values); // 123
    });
    

    手动实现 Promise.all()


    有的面试会让实现Promise.all()方法,这个还算简单的,有的还要手撕Promise

    首先来理清一下实现思路:

    • 遍历执行传入的Promise实例。

    • 如果传入的参数成员有不是Promise实例的,会直接调用Promise.resolve()进行处理。

    • 返回一个新的Promise实例,分两种情况返回结果。

    • fulfilled状态返回的数组结果要按顺序排列。

    实现代码如下:

    function promiseAll(promises) {
      // 返回一个新的 Promise 实例
      return new Promise((resolve, reject) => {
        let count = 0;
        let promisesNum = promises.length;
        let resolvedValue = new Array(promisesNum);
    	for (let i=0; i<promisesNum; i++) {
            // 用 Promise.resolve() 处理传入参数
            Promise.resolve(promises[i])
               .then((value) => {
                  // 顺序排列返回结果
                  resolvedValue[i] = value;
                  if (++count == promisesNum) {
                      return resolve(resolvedValue);
                  }
                })
                .catch((err) => {
                  // 返回第一个 rejected 状态的结果
                  return reject(err);
            });
        }
      });
    }
    

    测试如下:

    // 成功的情况
    const p1=Promise.resolve(1);
    const p2=Promise.resolve(2);
    const p3=Promise.resolve(3);
    
    promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
    
    // 失败的情况
    const p1=Promise.resolve(1);
    const p2=Promise.reject('失败');
    const p3=Promise.resolve(3);
    
    promiseAll([p1, p2, p3])
      .then(console.log)
      .catch(console.log); // 失败
    

    备注


    春招开始了,有点不敢投,踌躇着,也焦虑着。

  • 相关阅读:
    iframe之间操作记录
    Windows平台下nginx跨域配置
    git 常用命令
    单引号和双引号
    Mybatis Generator配置详解
    IOS仿桌面拖动桌面图标
    Bash Shell基础笔记
    mysql服务启动异常:windows无法启动Mysql服务,位于本地计算机上的错误1053 解决
    lombok笔记----Lombok常用注解
    thrift笔记----大体上thrift知识
  • 原文地址:https://www.cnblogs.com/guolao/p/10433577.html
Copyright © 2011-2022 走看看