• Promise使用手册


    导读

    Promise问世已久, 其科普类文章亦不计其数. 遂本篇初衷不为科普, 只为能够温故而知新.
    比如说, catch能捕获所有的错误吗? 为什么有些时候会抛出”Uncaught (in promise) …”? Promise.resolve 和 Promise.reject 处理Promise对象时又有什么不一样的地方?

    Promise

    引子

    阅读此篇之前, 我们先体验一下如下代码:

    setTimeout(function() {
      console.log(4)
    }, 0);
    new Promise(function(resolve) {
      console.log(1);
      for (var i = 0; i < 10000; i++) {
        i == 9999 && resolve()
      }
      console.log(2);
    }).then(function() {
      console.log(5)
    });
    console.log(3);

    这里先卖个关子, 后续将给出答案并提供详细分析.
    和往常文章一样, 我喜欢从api入手, 先具象地了解一个概念, 然后再抽象或扩展这个概念, 接着再谈谈概念的具体应用场景, 通常末尾还会有一个简短的小结. 这样, 查询api的读者可以选择性地阅读上文, 希望深入的读者可以继续剖析概念, 当然我更希望你能耐心地读到应用场景处, 这样便能升华对这个概念或技术的运用, 也能避免踩坑.

    new Promise

    Promise的设计初衷是避免异步回调地狱. 它提供更简洁的api, 同时展平回调为链式调用, 使得代码更加清爽, 易读.
    如下, 即创建一个Promise对象:

    const p = new Promise(function(resolve, reject) {
      console.log('Create a new Promise.');
    });
    console.log(p);


    创建Promise时, 浏览器同步执行传入的第一个方法, 从而输出log. 新创建的promise实例对象, 初始状态为等待(pending), 除此之外, Promise还有另外两个状态:

    • fulfilled, 表示操作完成, 实现了. 只在resolve方法执行时才进入该状态.
    • rejected, 表示操作失败, 拒绝了. 只在reject方法执行时或抛出错误的情况下才进入该状态.

    如下图展示了Promise的状态变化过程(图片来自MDN):

    从初始状态(pending)到实现(fulfilled)或拒绝(rejected)状态的转换, 这是两个分支, 实现或拒绝即最终状态, 一旦到达其中之一的状态, promise的状态便稳定了. (因此, 不要尝试实现或拒绝状态的互转, 它们都是最终状态, 没法转换)
    以上, 创建Promise对象时, 传入的回调函数function(resolve, reject){}默认拥有两个参数, 分别为:

    • resolve, 用于改变该Promise本身的状态为实现, 执行后, 将触发then的onFulfilled回调, 并把resolve的参数传递给onFulfilled回调.
    • reject, 用于改变该Promise本身的状态为拒绝, 执行后, 将触发 then | catch的onRejected回调, 并把reject的参数传递给onRejected回调.

    Promise的原型仅有两个自身方法, 分别为 Promise.prototype.then , Promise.prototype.catch . 而它自身仅有四个方法, 分别为 Promise.reject , Promise.resolve , Promise.all , Promise.race .

    then

    语法: Promise.prototype.then(onFulfilled, onRejected)
    用于绑定后续操作. 使用十分简单:

    p.then(function(res) {
      console.log('此处执行后续操作');
    });
    // 当然, then的最大便利之处便是可以链式调用
    p.then(function(res) {
      console.log('先做一件事');
    }).then(function(res) {
      console.log('再做一件事');
    });
    // then还可以同时接两个回调,分别处理成功和失败状态
    p.then(function(SuccessRes) {
      console.log('处理成功的操作');
    }, function(failRes) {
      console.log('处理失败的操作');
    });

    不仅如此, Promise的then中还可返回一个新的Promise对象, 后续的then将接着继续处理这个新的Promise对象.

    p.then(function(){
      return new Promise(function(resolve, reject) {
        console.log('这里是一个新的Promise对象');
        resolve('New Promise resolve.');
      });
    }).then(function(res) {
      console.log(res);
    });

    那么, 如果没有指定返回值, 会怎么样?
    根据Promise规范, then或catch即使未显式指定返回值, 它们也总是默认返回一个新的fulfilled状态的promise对象.

    catch

    语法: Promise.prototype.catch(onRejected)
    用于捕获并处理异常. 无论是程序抛出的异常, 还是主动reject掉Promise自身, 都会被catch捕获到.

    new Promise(function(resolve, reject) {
      reject('该prormise已被拒绝');
    }).catch(function(reason) {
      console.log('catch:', reason);
    });

    同then语句一样, catch也是可以链式调用的.

    new Promise(function(resolve, reject){
      reject('该prormise已被拒绝');
    }).catch(function(reason){
      console.log('catch:', reason);
      console.log(a);
    }).catch(function(reason){
      console.log(reason);
    });

    以上, 将依次输出两次log, 第一次输出promise被拒绝, 第二次输出”ReferenceError a is not defined”的堆栈信息.

    catch能捕获哪些错误
    那是不是catch可以捕获所有错误呢? 可以, 怎么不可以, 我以前也这么天真的认为. 直到有一天我执行了如下的语句, 我就学乖了.

    new Promise(function(resolve, reject){
      Promise.reject('返回一个拒绝状态的Promise');
    }).catch(function(reason){
      console.log('catch:', reason);
    });

    执行结果如下:

    为什么catch没有捕获到该错误呢? 这个问题, 待下一节我们了解了Promise.reject语法后再做分析.

    Promise.reject

    语法: Promise.reject(value)
    该方法返回一个拒绝状态的Promise对象, 同时传入的参数作为PromiseValue.

    //params: String
    Promise.reject('该prormise已被拒绝');
    .catch(function(reason){
      console.log('catch:', reason);
    });
    //params: Error
    Promise.reject(new Error('这是一个error')).then(function(res) {
      console.log('fulfilled:', res);
    }, function(reason) {
      console.log('rejected:', reason); // rejected: Error: 这是一个error...
    });

    即使参数为Promise对象, 它也一样会把Promise当作拒绝的理由, 在外部再包装一个拒绝状态的Promise对象予以返回.

    //params: Promise
    const p = new Promise(function(resolve) {
      console.log('This is a promise');
    });
    Promise.reject(p).catch(function(reason) {
      console.log('rejected:', reason);
      console.log(p == reason);
    });
    // "This is a promise"
    // rejected: Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
    // true

    以上代码片段, Promise.reject(p) 进入到了catch语句中, 说明其返回了一个拒绝状态的Promise, 同时拒绝的理由就是传入的参数p.

    错误处理

    我们都知道, Promise.reject返回了一个拒绝状态的Promise对象. 对于这样的Promise对象, 如果其后续then | catch中都没有声明onRejected回调, 它将会抛出一个 “Uncaught (in promise) …”的错误. 如上图所示, 原语句是 “Promise.reject(‘返回一个拒绝状态的Promise’);” 其后续并没有跟随任何then | catch语句, 因此它将抛出错误, 且该错外部的Promise无法捕获.
    不仅如此, Promise之间泾渭分明, 内部Promise抛出的任何错误, 外部Promise对象都无法感知并捕获. 同时, 由于promise是异步的, try catch语句也无法捕获其错误.
    因此养成良好习惯, promise记得写上catch.
    除了catch, nodejs下Promise抛出的错误, 还会被进程的unhandledRejection 和 rejectionHandled事件捕获.
    链式写法的好处
    请看如下代码:

    new Promise(function(resolve, reject) {
      resolve('New Promise resolve.');
    }).then(function(str) {
      throw new Error("oops...");
    },function(error) {
        console.log('then catch:', error);
    }).catch(function(reason) {
        console.log('catch:', reason);
    });
    //catch: Error: oops...

    可见, then语句的onRejected回调并不能捕获onFulfilled回调内抛出的错误, 尾随其后的catch语句却可以, 因此推荐链式写法.

    Promise.resolve

    语法: Promise.resolve(value | promise | thenable)
    thenable 表示一个定义了 then 方法的对象或函数.
    参数为promise时, 返回promise本身.
    参数为thenable的对象或函数时, 将其then属性作为new promise时的回调, 返回一个包装的promise对象.(注意: 这里与Promise.reject直接包装一个拒绝状态的Promise不同)
    其他情况下, 返回一个实现状态的Promise对象, 同时传入的参数作为PromiseValue.

    //params: String
    //return: fulfilled Promise
    Promise.resolve('返回一个fulfilled状态的promise').then(function(res) {
      console.log(res); // "返回一个fulfilled状态的promise"
    });
    //params: Array
    //return: fulfilled Promise
    Promise.resolve(['a', 'b', 'c']).then(function(res) {
      console.log(res); // ["a", "b", "c"]
    });
    //params: Promise
    //return: Promise self
    let resolveFn;
    const p2 = new Promise(function(resolve) {
      resolveFn = resolve;
    });
    const r2 = Promise.resolve(p2);
    r2.then(function(res) {
      console.log(res);
    });
    resolveFn('xyz'); // "xyz"
    console.log(r2 === p2); // true
    //params: thenable Object
    //return: 根据thenable的最终状态返回不同的promise
    const thenable = {
      then: function(resolve, reject) { //作为new promise时的回调函数
        reject('promise rejected!');
      }
    };
    Promise.resolve(thenable).then(function(res) {
      console.log('res:', res);
    }, function(reason) {
      console.log('reason:', reason);
    });

    可见, Promise.resolve并非返回实现状态的Promise这么简单, 我们还需基于传入的参数动态判断.
    至此, 我们基本上不用期望使用Promise全局方法中去改变其某个实例的状态.

    • 对于Promise.reject(promise), 它只是简单地包了一个拒绝状态的promise壳, 参数promise什么都没变.
    • 对于Promise.resolve(promise), 仅仅返回参数promise本身.

    Promise.all

    语法: Promise.all(iterable)
    该方法接一个迭代器(如数组等), 返回一个新的Promise对象. 如果迭代器中所有的Promise对象都被实现, 那么, 返回的Promise对象状态为”fulfilled”, 反之则为”rejected”. 概念上类似Array.prototype.every.

    //params: all fulfilled promise
    //return: fulfilled promise
    Promise.all([1, 2, 3]).then(function(res){
      console.log('promise fulfilled:', res); // promise fulfilled: [1, 2, 3]
    });
    //params: has rejected promise
    //return: rejected promise
    const p = new Promise(function(resolve, reject){
      reject('rejected');
    });
    Promise.all([1, 2, p]).then(function(res){
      console.log('promise fulfilled:', res);
    }).catch(function(reason){
      console.log('promise reject:', reason); // promise reject: rejected
    });

    Promise.all特别适用于处理依赖多个异步请求的结果的场景.

    Promise.race

    该方法接一个迭代器(如数组等), 返回一个新的Promise对象. 只要迭代器中有一个Promise对象状态改变(被实现或被拒绝), 那么返回的Promise将以相同的值被实现或拒绝, 然后它将忽略迭代器中其他Promise的状态变化.

    Promise.race([1, Promise.reject(2)]).then(function(res){
      console.log('promise fulfilled:', res);
    }).catch(function(reason){
      console.log('promise reject:', reason);
    });
    // promise fulfilled: 1

    如果调换以上参数的顺序, 结果将输出 “promise reject: 2”. 可见对于状态稳定的Promise(fulfilled 或 rejected状态), 哪个排第一, 将返回哪个.
    Promise.race适用于多者中取其一的场景, 比如同时发送多个请求, 只要有一个请求成功, 那么就以该Promise的状态作为最终的状态, 该Promise的值作为最终的值, 包装成一个新的Promise对象予以返回.
    在 Fetch进阶指南 一文中, 我曾利用Promise.race模拟了Promise的abort和timeout机制.

    Promises/A+规范的要点

    promise.then(onFulfilled, onRejected)中, 参数都是可选的, 如果onFulfilled或onRejected不是函数, 那么将忽略它们.
    catch只是then的语法糖, 相当于promise.then(null, onRejected).

    任务队列之谜

    终于, 我们要一起来看看文章起始的一道题目.

    setTimeout(function() {
      console.log(4)
    }, 0);
    new Promise(function(resolve) {
      console.log(1);
      for (var i = 0; i < 10000; i++) {
        i == 9999 && resolve()
      }
      console.log(2);
    }).then(function() {
      console.log(5)
    });
    console.log(3);

    这道题目来自知乎(机智的你可能早已看穿, 但千万别戳破

  • 相关阅读:
    slf4j的简单用法以及与log4j的区别
    [转]Git 代码撤销、回滚到任意版本(当误提代码到本地或master分支时)
    【转】IDEA 中配置文件properties文件中文乱码解决
    Python+Selenium练习篇之3-浏览器滚动条操作
    selenium操作下拉滚动条的几种方法
    python利用unittest进行测试用例执行的几种方式
    安装和使用 Python
    PM2实用入门指南
    linux清除缓存
    【centos6.6环境搭建】Github unable to access SSL connect error出错处理
  • 原文地址:https://www.cnblogs.com/MartinLee/p/7622519.html
Copyright © 2020-2023  润新知