最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Promise解决的业务痛点

    正文概述 掘金(天道酬勤dg)   2020-12-27   390

    1. Promise 诞生

    Promsie 是一种新的用于处理 js 异步逻辑的方法。回想我们曾经是如何处理 js 异步问题的 —— 利用回调,但是回调会引出一系列新的问题,比如回调地狱以及回调的信任问题。然后各开发者们都在寻求新的解决异步问题的方式,于是,Promise 诞生了。它很好地解决了回调处理异步可能带来的很多问题,下面我们就来一步步看看,相对于回调,Promise 有什么优点,为什么会选择它来处理异步的问题。

    2. Promise 决议值

    new Promise(function fun(resolve, reject){}),其中传入的fun函数会立即执行,他有两个参数:resolvereject,这些是 Promise 的决议函数,resolve 标识决议,reject 标识拒绝,如果执行 reject 拒绝操作,必须传入一个拒绝原因。

    2.1 决议结果的分类

    注:前方高能,很重要:

    一个 promise 必须处于以下三种状态之一:pending/fulfilled/rejected。pending 标识 Promise 还未决议,fulfilled 和 rejected 是 Promise 的决议结果。Promise 决议的结果也只有这个 2 个,直观翻译过来就是完成拒绝其中调用reject()得到的一定是rejected;而调用resolve()得到的就不一定是fulfilled

    这是因为resolve()函数,会根据接收到的参数不同,执行不同的解析逻辑,从而得到的决议结果也会不同:

    1. 若参数是一个 promise 实例,resolve 会直接返回这个 promise。也就是说,如果这个 promise 的决议结果是 fulfilled,则 resolve() 的决议结果也会是 fulfilled;如果 promise 实例的决议结果是 rejected,resolve() 的决议结果也会是 rejected,如下:
    let pro1 = Promise.reject("Some error happend");
    let pro2 = Promise.resolve(pro1);
    pro1 === pro2; // true
    // 如上,pro1和pro2其实指向同一个promise实例,而其决议结果都是 rejected
    
    1. 若参数是一个 thenable 值(一个拥有 then 方法的对象或者函数),则会对这个 thenable 进行展开,如果 thenable 展开得到一个拒绝状态,则 resolve() 的决议结果也会是一个拒绝状态。
    let obj = {
      then: function(resolve, reject) {
        reject("Some error happend");
      },
    };
    
    let pro = Promise.resolve(obj);
    pro.then(
      function fulfilled() {
        // 永远到不了这里
      },
      function rejected(err) {
        console.log(err); // Some error happend
      }
    );
    

    如上例,如果 thenable 中的resolvereject一直未调用,则pro会一直是未决议状态,即一直是pending,那么pro.then也一直不会调用。

    1. 若参数是其他普通值:则会直接进入fulfilled状态,并将这个普通值作为参数传入 then 方法中的第一个函数:
    let pro = Promise.resolve(1);
    pro.then(
      function fulfilled(data) {
        console.log(data); // 1
      },
      function rejected(err) {
        // 永远到不了这里
      }
    );
    

    综上 3 点,我们就更能明白resolvereject的各自的作用以及他们之间的区别了:

    1. reject 标识直接拒绝,进入rejected状态了;而且 reject 并不会像 resolve 一样,对接收到的值进行展开。如果给 reject 传入一个 Promise,他会原封不动的把这个 Promise 设置为拒绝理由,而不是其底层的值。

    2. resolve 标识决议,意为现在还不能直接确定是fulfilled还是rejected,还需进一步判断,才能得到结果。而最终的决议结果有可能是fulfilled,也有可能是rejected,这个决议结果就是最终 Promise 的决议结果。所以,我们把 resolve 称为决议而不是完成,是非常恰当合理的。

    2.2 决议值的不变性

    Promise 很重要的一个特性,就是一旦决议,其决议值就不会再更改,可以按照需求多次查看。

    3. 判断 Promise 实例

    在 Promise 领域,有一个很重要的点就是如何确定某个值是不是一个 Promise,或者行为类似于 Promise 的值? 你首先想到的可能就是,既然实例是通过 new Promise()创建出来的,那么就可以通过p instanceof Promise的方式来判断。但是很遗憾,这个方式并不足以作为检查方法,原因有许多:

    1. 你得到的 Promsie 的值很可能是从其他浏览器窗口,通过 iframe 方式接收到的,这个浏览器窗口的 Promise 与你当前窗口的 Promise 不同,这是使用 instanceof 判断就会不准确;

    2. 某些库或者框架可能会实现自己的 Promise,你得到的实例可能就不会来自于当前环境的 Promise 构造函数。

    解决方法:

    首先可以使用上面的 instanceof 判断,若为 true,则直接判断为是 Promise 实例;若为 false,则可以使用接下来的“鸭子类型”判断 —— 如果它看起来像只鸭子,叫起来也像只鸭子,那么它就是一只鸭子:

    if (
      p !== null &&
      (typeof p === "object" || typeof p === "function") &&
      typeof p.then === "function"
    ) {
      // 就假定这是一个thenable(Promise 实例)
    } else {
      // 不是一个thenable
    }
    

    4. 解决的痛点

    回想我们曾经利用回调编写异步代码的时候,把一个回调传入另一个工具函数,可能出现的如下问题:

    1. 调用过早;
    2. 调用过晚;
    3. 回调未调用;
    4. 调用回调次数过少或过多;
    5. 未能传递所需要的参数;
    6. 吞掉可能出现的错误或者异常;
    7. 回调地狱

    针对这些问题,Promise 都给出了有效的解决方案。Promise 解决了通过回调表达异步的可信任性问题以及可能造成的回调地狱的问题。

    4.1 调用过早

    对 Promise 调用 then(...)的时候,这个 Promise 肯定已经决议,而且传入 then 的回调函数总是会被异步调用。这就很好的解决了回调函数有时同步完成,有时异步完成导致的竞态条件。

    4.2 调用过晚

    Promise 决议之后,通过 then 注册的回调就会被添加到事件队列,然后在下一个异步事件点上被调用。后一个 then 注册的回调,一定会等前一个 then 的回调执行完成之后,才会执行;前面的 then 中注册的回调也无法阻挡后面的 then 中回调的执行。所以,then 中的回调,都能按照你所能预期的顺序进行执行,而不会出现前面的回调比后面的调用晚的问题。

    4.3 回调未调用

    回调要被调用,Promise 就一定要决议,那么如果 Promise 永远没有决议呢?即使这样,Promise 也提供了解决方案,一种称为竞态的机制:

    function timeoutPromise(delay) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject("Timeout!");
        }, delay);
      });
    }
    
    Promise.race([fun(), timeoutPromise(60 * 1000)]).then(
      () => {
        // fun()及时完成
      },
      (err) => {
        // fun()被拒绝,或者超时
        // 通过err来查看是哪种情况
      }
    );
    

    通过以上模式,我们就可以保证fun()的调用,永远会有一个输出信号,防止永久挂起。

    4.4 调用次数过少或过多

    回调被调用的正确次数应该是 1,调用次数过少就就是未调用,上面已经解释过其解决方案;调用次数过多也很好处理:

    Promise 只能被决议一次,即使你试图多次调用 resolve 或者 reject,Promise 也只会接收第一次决议结果,而忽略后续所有调用。Promise 决议完成后,就会把 then 中注册人的回调加入事件队列等待执行,所以,then 中注册的回调也只会被调用一次。

    4.5 未能传递所需要的参数

    如果你传入一个值给 resolve 或者 reject,一旦 Promise 决议,他都肯定会被传给 then 中注册的回调函数,这一点是确定的。所以只要你传了一个值给 resolve 或者 reject,就不会出现在 then 的回调中接收不到参数的情况。

    有一点需要注意的:resolve 和 reject 都只接收一个参数,所以,如果你传入多个参数,第一个参数之后剩余的参数,就会被默默忽略。如果需要传多个值,就需要封装到一个数组或者对象中。

    4.6 吞掉可能出现的错误或者异常

    当调用 Promise 时,如果你直接调用了 reject,那么这个 Promise 就会直接拒绝。其实不只是调用 reject,如果你在代码执行中,某个时间点上出现了一个 JavaScript 错误,比如 TypeError 或者 ReferenceError,那么这个错误也会被捕捉,并且会使这个 Promise 直接拒绝。

    这一点很重要,其有效地防止了一个方法出错时可能引起同步响应,而成功时却是异步响应的差异化结果。

    4.7 回调地狱

    Promise 很好的实现了关注点分离,Promise 决议前不需要关注决议后会干什么,决议后的逻辑也不需要关注决议的具体过程。再配合 then(...)方法的链式调用,因此,能很好的避免回调地狱的产生。

    5. 链式流

    5.1 then 返回值

    每次对 Promise 调用 then(...),它都会返回一个新的 Promise,因此我们可以将其链接起来,实现 then 的链式调用;

    5.2 新 Promise 的决议值

    新返回的 Promise 的决议值,取决于调用 then(...)中和注册的回调的执行结果,then 中执行的是fulfilled回调,不代表返回的 Promise 的决议值就也是 fulfilled;then 中执行的是rejected回调,不代表返回的 Promise 的决议值就也是 rejected;这要取决于 then 中回调的执行过程及结果:

    不管 then 中执行的人是fulfilled还是rejected回调,如果执行的那个回调函数正常结束,没有抛出错误,那么新返回的 Promise 的决议值就也是 fulfilled;反之,如果执行的那个回调函数执行出错,或者显性的抛出错误,那么新返回的 Promise 的决议值就也是 rejected;如下:

    • 看下面的代码会打印出什么?
    Promise.reject("some error")
      .then(null, (err) => err)
      .then(
        (data) => {
          console.log("fulfilled: ", data);
        },
        (err) => {
          console.log("rejected: ", err);
        }
      );
    
    // 上面的代码会打印出:
    // fulfilled: some error
    
    • 这一段代码又会打印出什么?
    Promise.resolve(null)
      .then(
        (data) => data.toString(),
        (err) => err
      )
      .then(
        (data) => {
          console.log("fulfilled: ", data);
        },
        (err) => {
          console.log("rejected: ", err);
        }
      );
    
    // 上面的代码会打印出:
    // rejected: TypeError: Cannot read property 'toString' of null
    

    5.3 then 回调缺省

    如果传入 then 的回调函数fulfilledrejected其中之一缺省或者值为null,则 Promise 会自动添加一个默认处理函数,这个处理函数会将接收到的任何值直接返回并在这个链上传递,直到遇到显示定义的回调函数能接受这个值。

    Promise.resolve(1)
      .then(null, () => {})
      .then(
        (data) => {
          console.log(data); // 1
        },
        (err) => {
          console.log(err);
        }
      );
    
    Promise.reject("Some error happend")
      .then(null, null)
      .then(
        (data) => {
          console.log(data);
        },
        (err) => {
          console.log(err); // Some error happend
        }
      );
    

    5.4 回调函数返回 Promise 的处理方式

    若 then 的成功或拒绝处理函数返回了一个 Promise,则处理方式会类似于我们前面说到的 resolve()接收到一个 Promise 的处理方式,这个 Promise 会被展开,他的决议值会作为当前 then 返回的链接 Promise 的决议值。因此下一个链接的 then,就会等这个被返回的 Promise 被决议之后,才会执行。

    function delay(time) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, time);
      });
    }
    
    Promise.resolve()
      .then(() => {
        console.log("call step1");
        return delay(2000);
      })
      .then(() => {
        console.log("call step2");
      });
    

    上面第一个 then 的完成回调执行之后,返回了一个 Promise 吗,而这个 Promise 在 2000ms 之后才决议,因此,第一个 then 返回的链接 Promise 也要等 2000ms 之后才会决议。链接 Promise 决议之后,第二个 then 的回调才会添加到事件队列等待执行。也就意味着,call step2会比call step1至少晚 2000ms 输出。

    6. Promise 静态方法和实例方法

    本文主要介绍 Promise 解决的业务痛点,以及为啥是可信任的,对于其他的静态方法和实例方法,读者可以分别查阅 MDN 上的相关文档,上面都有详细的介绍,这里我就不再做重复的事儿了。如果以上描述有什么不恰当的,欢迎指正;如果觉得对您有帮助,麻烦点个赞(^-^)。如果您还想更进一步查看 Promsie/A+ 规范了解更多信息,可以查看Promises/A+ 规范(译本)。

    MDN Promise 静态方法

    MDN Promise 原型方法


    起源地下载网 » Promise解决的业务痛点

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元