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

    正文概述 掘金(_李国庆)   2020-12-27   399

    前言

    本文会讲解 ES6 中的 promise,但是并不通过类似于笔记文档式的方法进行书写。我会从 promise 诞生之前讲起,在什么坏境下出现?为什么出现?早期的社区中是怎么实现的,直到成为语言的规范的整个过程。之后介绍核心语法,以及在实战中的使用。

    为了大家的理解,我会尽量用通俗易懂的方法谈及,最后不论之前你会不会 promise ,都希望可以通过此文对 promise 有一个更加全新的认识。

    Promise 的历史

    你大可不必认为这是单纯的来讲历史而觉得不重要,相反这对你理解事物本身有锦上添花的妙处。

    Promise 其实是一个古老的概念,在很多的常见语言的标准库中都有 Promise 关联的特性。即使没有,像在 javascript 中,早期也是由第三方即社区已经实现好了。

    JavaScript 中,最早得到广泛使用的 PromisejQuery 中的 jQuery.Deferred()。你或许没有使用过 jq ,又或者不知道这个方法,但是你绝对听说过 jQuery 中的 ajax 吧。这其实就是早期的 Promise

    最终其终于称为了语言的规范存在于 ES6 中。

    为什么需要 Promise

    假设我们现在需要完成大量的异步任务,但是这些异步任务的执行是有条件的,就是必须等待上一个异步任务成功后执行

    这通常在异步的网络请求或者 Node.js 中的异步操作用很常见。 比如一段异步读取文件的代码:

    const fs = require('fs');
    
    fs.readFile('1.txt', (_, data) => {
      console.log(data.toString());
      fs.readFile('2.txt', (_, data) => {
        console.log(data.toString());
        fs.readFile('3.txt', (_, data) => {
          console.log(data.toString());
        })
      })
    });
    

    可以看到这个嵌套的层级是很严重的,当我们有更多需要依次执行的任务时,就会出现回调地狱

    为了解决回调地狱的问题,Promise 就诞生了。

    社区中的实现

    这里我会给您使用图文的方式阐述早期在 jQuery 中是如何实现 Promise 的,但是并不会带你手写其内部的实现,旨在让你对 Promise 有一个大概的初步认识,并且掌握其原理,了解它的运转流程。

    容器

    你可以想象现在有一个容器,你可以把他当成一个数组。容器中有很多的回调函数等待执行

    JavaScript Promise 篇

    需要注意的是容器中的这些回调函数还没有执行。我们可以对这个容器进行以下操作:

    • 添加回调函数(add
    • 删除回调函数(remove
    • 依次执行所有的回调函数(fire
    • 并且当我们 fire 后还可以继续往容器中 add 回调函数,此时新加的函数会被立即执行

    三个容器

    我们已经对上述的容器有了一个认识,其实 Promise 就是管理了这样的三个容器,分别对应三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败):

    JavaScript Promise 篇

    最终的状态只由异步任务的结果决定任何其他的操作都没有办法改变这个状态

    Promise 对象的状态改变只有两种可能:

    • pending 变为 fulfilled
    • pending 变为 rejected

    社区版 Promise 的原理就是维护着三个容器,当异步任务的结果确定后会自动切换状态,然后找到当前状态所管理着的容器,依次执行容器中的回调函数。

    接着,Promise 的实例对象拥有 thencatch 方法,他们的原理是:

    • then 方法可以接受两个回调函数,第一个回调函数会被添加到 fulfilled 这个状态所管理的容器中;第二个回调函数是可选的,会被添加到 rejected 这个状态所管理的容器中。
    • catch 方法可以接受一个回调函数,会被添加到 rejected 这个状态所管理的容器中。

    当异步任务的状态确定后,例如从 pendingfulfilled ,首先会执行 fulfilled 所管理的容器中的回调函数,接着如果有 then 方法,第一个参数会被继续添加到当前的容器。由上面提交的容器的最后一条性质可以知道,新添加的方法会被立即执行,所以当前 then 函数中的第一个回调函数会被立即执行。

    正式学习 Promise

    在了解了 Promise 的一些基础概念以及早期社区版本的一个实现原理后,我们再来学习 ES6 中的 Promise ,这时就会觉得轻松易懂好上手。

    Promise 的构造函数

    想要构造一个 Promise,基本的用法如下(伪代码):

    const promise = new Promise((resolve, reject) => {
      if (...) { // succeed
        resolve(result);
      } else { // failed
        reject(Error(errMessage));
      }
    });
    

    使用 Promise 封装 ajax

    了解了基础的 Promise 使用后,我们可以讲平时经常使用的 XMLHttpRequest 进行封装:

    const ajax = (function() {
      'use strict';
      const DONE = 4, OK = 200;
    
      // 该方法处理 get 请求的参数
      function serialize(obj) {
        const str = [];
        for (const p in obj) {
          if (obj.hasOwnProperty(p)) {
            str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
          }
        }
        return str.join('&');
      }
      // 核心方法
      function core(method, url, data, headers) {
        if (method === 'GET' && data) {
          url += '?' + serialize(data);
        }
    
        return new Promise((resolve, reject) => {
          const xhr = new XMLHttpRequest();
          xhr.open(method, url, true);
    
          if (headers) {
            for (const key in headers) {
              if (headers.hasOwnProperty(key)) {
                xhr.setRequestHeader(key, headers[key]);
              }
            }
          }
    
          xhr.onreadystatechange = e => {
            if (xhr.readyState === DONE) {
              if (xhr.status === OK) {
                resolve(xhr.responseText);
              } else {
                reject(Error(xhr.statusText));
              }
            }
          };
    
          xhr.onerror = e => {
            reject(Error('XMLHttpRequest failed'));
          };
    
          if (method === 'GET' || !data) {
            xhr.send();
          } else {
            xhr.send(data);
          }
        });
      }
      
      // 向外暴露四个可用的方法
      return {
        'get': (url, data, headers) => {
          return core('GET', url, data, headers);
        },
        'post': (url, data, headers) => {
          return core('POST', url, data, headers);
        },
        'put': (url, data, headers) => {
          return core('PUT', url, data, headers);
        },
        'delete': (url, headers) => {
          return core('DELETE', url, headers);
        }
      };
    })();
    

    相信对上面封装 ajax 的操作后你应该对 Promise 有了更深刻的体会,就只需要在异步成功时调用 resolve ,失败时调用 reject 并且传递响应的参数就可以了。

    实例方法

    介绍完 Promise 的构造函数后,接着来介绍它的实例方法。我们使用上面已经封装好的 ajax 来进行测验。

    then

    第一个实例方法就是已经提到过的 then 方法,它接受两个参数,第一个参数时成功的回调函数,第二个参数是失败的回调函数。

    我们使用上面封装的 ajax 来请求 dog.ceo/api/breeds/… 网址,我们可以得到一张随机的狗狗的照片地址:

    const img = document.querySelector('img');
    ajax.get('https://dog.ceo/api/breeds/image/random')
    .then(res => {
      res = JSON.parse(res);
      img.src = res.message;
    });
    

    通过网络请求,在 then 方法中传递一个回调函数,他会默认在成功后执行,并且成功后返回封装的方法中的 resolve 方法的参数。所以 res 是一个 JSON 字符串,这里我们对其解析后赋值给 imgsrc 属性。

    这时可以在页面中看到随机的狗狗的照片:

    JavaScript Promise 篇

    当然如果异步任务失败了,你可以向 then 方法中传递第二个参数,它会在失败后自动执行,并且参数是一个错误信息。

    catch

    catch 方法使用时传递一个回调函数:promise.catch(onRejected),它等同于 promise.then(undefined, onRejected) 。也就是在失败后会调用其回调函数。

    finally

    最后一个实例方法是 finally ,接受一个回调函数作为参数,该方法在不管 Promise 最后的状态如何都会调用,不过他是 ES2018 引入的标准。

    ajax.get('https://dog.ceo/api/breeds/image/random')
    .then(res => {
      res = JSON.parse(res);
      img.src = res.message;
    }).catch(err => {
      console.log(err);
    }).finally(() => {
      console.log('请求结束');
    });
    

    这样,不论失败还是成功,都会打印请求结束

    JavaScript Promise 篇

    静态方法

    介绍完了实例方法后,有几个很重要的静态方法需要我们记住并熟练掌握:

    • all
    • race
    • resolve
    • reject

    Promise.all

    Promise.all 方法接受一个数组作为参数,只有参数中所有的 promise 实例都 resolve 后才会执行成功的回调函数,只要有一个 reject 就会执行失败的回调函数。

    Promise.all([promise1, promise2, promise3]).then(res => {
        console.log(res); // [res1, res2, res3]
    }); 
    

    Promise.race

    Promise.race 方法同样接受一个数组作为参数,但是只要有一个 promise 实例的状态变为成功就会执行成功的回调函数,并且参数为最快的那个任务的结果。只有全部任务失败后才会执行失败的回调函数。

    Promise.race([promise1, promise2, promise3]).then(res => {
        console.log(res); // res1 if promise1 is resolved first, etc.
    }); 
    

    Promise.resolve

    Promise.resolve 快速创建一个 Promise ,如果参数原来就是 Promise,返回值跟原来的 Promise 一致。如果原来不是 Promise,参数会被包装给 then 的第一个参数函数。

    该方法可以用来封装不确定是不是 Promise 的值。

    Promise.reject

    Promise.reject 快速创建一个被 rejectPromise ,如果参数是 Promise,返回值跟原来的 Promise 一致。如果原来不是 Promise,参数值会被包装给 then 的第二个参数函数或者 catch。

    该方法可以用来封装不确定是不是 Promise 的值。

    使用 Promise

    学习完所有的 Promise 相关的 API 后,我们可以在之后的项目或者练习中面对异步操作时尽可能地使用 Promise 来书写,这样写出来得代码既可以回避回调地狱的问题,也可以让代码更具有可读性。例如本文中我们已经使用 Promise 来封装了一个 ajax

    当然你也可以配合 async 函数来使用更深刻得体会异步操作得同步写法。

    总结

    我们在学习一项新颖得技术得时候一定要思其所以然,从为什么到怎么样再到实线得过程完全掌握它。更要在实践中体验新技术的优缺点并总结。

    比如说了一大推 Promise 的优点,它有什么缺点呢,主要是有三个:

    • 无法取消 Promise,一旦创建就会立即执行无法中途取消。
    • 如果不设置回调函数,内部抛出的错误我们无法感知。
    • 当处于 pending 状态时,无法得知目前的进展(刚刚开始还是即将完成)。

    起源地下载网 » JavaScript Promise 篇

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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