js 核心之异步编程和Promise - Go语言中文社区

js 核心之异步编程和Promise


js 核心之异步编程和Promise

何为异步

所谓“异步”,简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。

异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。

在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。

简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效果更高:

以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:异步就是从主线程发射一个子线程来完成任务。

异步的使用时间

在前端编程中(甚至后端有时也是这样),我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,界面将无法处理其他请求。

现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。

为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。

为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。

回调函数

JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。回调函数的英语名字callback,直译过来就是"重新调用"。

 //回调地狱
      function fn1(arg) {
        setTimeout(function () {
          console.log("1");
          arg(); 
        }, 5000);
      }
      
      function fn2(arg) {
        setTimeout(function () {
          console.log("2");
          arg();
        }, 2000);
      }
     
      function fn3(arg) {
        console.log("3");
       arg();
      }
      //
      function fn4() {
        console.log("4");
      }
      //回调地狱
      fn1(function () {
        fn2(function () {
          fn3(function () {
            fn4();
          });
        });
      });

//
    function print() {
        document.getElementById("demo").innerHTML="RUNOOB!";
    }
    setTimeout(print, 3000);
//这段程序中的 setTimeout 就是一个消耗时间较长(3 秒)的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 "print",在命令行输出 "Time out"。

generator函数实现异步

    function fn1(arg) {
            setTimeout(function () {
              console.log("1");
              //arg(); 
            }, 1000);
          }
      function fn2(arg) {
        setTimeout(function () {
          console.log("2");
          ///arg();
        }, 2000);
      }
     
      function fn3(arg) {
        setTimeout(function () {
          console.log("3");
        }, 1000);
        // arg();
      }
    
      function fn4() {
        setTimeout(function () {
          console.log("4");
        }, 1000);
      }

      function* Generator() {
        yield fn1();
        yield fn2();
        yield fn3();
        yield fn4();
      }
      let iterator = Generator();
      console.log(iterator.next());
      console.log(iterator.next());
      console.log(iterator.next());
      console.log(iterator.next());

异步 AJAX

除了 setTimeout 函数以外,异步回调广泛应用于 AJAX 编程。常常用于请求来自远程服务器上的 XML 或 JSON 数据。一个标准的 XMLHttpRequest 对象往往包含多个回调:

先看一段代码,我们根据代码来讲

  <body>
    222
    <input type="button" value="发表" id="bt" />
    <script>
      function ajxa_Get() {
        // 1.创建ajax对象
          var xmlHttp = new XMLHttpRequest();
        // 2. 绑定监听  根据状态 判读值是否返回  readyState
        xmlHttp.onreadystatechange = function () {
          // readyState 0=>初始化 1=>载入 2=>载入完成 3=>解析 4=>完成
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            //获取请求 的值  responseText 获取请求过来的值 也是返回值
            //console.log(xmlHttp.responseText);
            document.getElementsByTagName("body")[0].innerHTML += xmlHttp.responseText;
          }
        };
        //3.绑定地址
        xmlHttp.open("GET", "data.html", true);
        //4.发送请求
        xmlHttp.send();
      }
      document.getElementById("bt").onclick = ajxa_Get;
    </script>
  </body>

注意data.html文件中的数据为一段文字“我爱你!!!”

运行结果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7jn1PTm-1599479955229)(C:UsersdellDesktop凯文的前端博客gifajax.gif)]

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequestAJAX 编程中被大量使用。

XMLHttpRequest构造函数

该构造函数用于初始化一个 XMLHttpRequest 实例对象。在调用下列任何其他方法之前,必须先调用该构造函数,或通过其他方式,得到一个实例对象。

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件更合理和更强大;
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;(就是传递给then方法, 还可以传一些参数)
reject函数的作用是:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

来段代码康康

//例子还是刚才那个ajax的案例
 	let promise = new Promise(function (resolve, reject) {
        // 1.创建ajax对象
        var xmlHttp = new XMLHttpRequest();
        // 2. 绑定监听  根据状态 判读值是否返回  readyState
        xmlHttp.onreadystatechange = function () {
          // readyState 0=>初始化 1=>载入 2=>载入完成 3=>解析 4=>完成
          if (xmlHttp.readyState == 4) {
            //获取请求 的值  responseText 获取请求过来的值 也是返回值
            // console.log(xmlHttp.responseText);
            if (xmlHttp.responseText !== "") {
              resolve(xmlHttp.responseText);//如果这里成功就返回对象的responseText
            } else {
              reject();//如果失败,就
            }
          }
        };
        //3.绑定地址
        xmlHttp.open("GET", "data1.html", true);
        //4.发送请求
        xmlHttp.send();
      });

      promise.then(
          function (value) {
            console.log(value);
            //如果还需要进行一个异步
          },
          function (value) {
            console.log(value);
          }
        ).catch(function (error) {
            console.log(error);
        });

Promise对象的特点

**(1)**对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)fulfilled(已成功)和rejected(已失败)。只有异步操作的结果(即在新建Promise对象时里面的some code的结果在通过判断之后执行的resolve或是reject函数),可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

**(2)**一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。也就是说, 你可以添加多个then,但是每个then都是要么resolved被激活或是rejected被激活

我们来几个简答的例子看看,进一步了解一下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2wOVecS-1599479955235)(C:UsersdellDesktop凯文的前端博客images2异步1.png)]
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pdXigueF-1599479955239)(C:UsersdellDesktop凯文的前端博客images2异步2.png)]

这里重点讲一下第三个例子

上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

Promise方法

.then方法

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。
它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

我们来段代码康康,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EHwFNq2B-1599479955244)(C:UsersdellDesktop凯文的前端博客images2异步4.png)]

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

.catch方法

Promise.prototype.catch方法是.then(null, rejected)或.then(undefined, rejected)的别名,用于指定发生错误时的回调函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8buzGqwt-1599479955250)(C:UsersdellDesktop凯文的前端博客images2异步5.png)]

.catch方法的触发问题

new Promise((reslove,reject)=>{
    return reslove(1);
    consloe.log("凯文");
    throw new Error("test");
    //调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
    //若当 Promise 状态已经变成resolved,再抛出错误是无效的;
});

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oizfMBCQ-1599479955253)(C:UsersdellDesktop凯文的前端博客images2异步6.png)]

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。(即, promise内部的报错不会终止整个js程序的运行, 而其他普通函数内一旦报错, 程序运行就会终止)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JGHTrwb-1599479955255)(C:UsersdellDesktop凯文的前端博客images2异步7.png)]

上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层,直接就在函数内报错了, 想要解决的话也很简单, 在.catch方法的后面再跟上一个catch就可以了

.all/.race方法

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p=Promise.all([p1,p2,p3]);

p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UwEnohoX-1599479955258)(C:UsersdellDesktop凯文的前端博客images2异步8.png)]

右侧代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。
该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,
因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

.race方法

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

const p=Promise.race([p1,p2,p3]);
//上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
.resolve方法

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

Promise.resolve等价下面的写法。

Promise.resolve("foo");
//等价于
new Promise(resolve=>resolve("foo"));

Promise.resolve方法的参数分成四种情况。
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。如右侧, 并把运行后resolve的参数传递给下一次的then

let thenable={
    then:function(resolve,reject){
        reslove(42);
    }
};
let p1=Promise.resolve(thenable);
p1.then(function(value){
    consloe.log(value);//42
})
.reject方法

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzhn049r-1599479955260)(C:UsersdellDesktop凯文的前端博客images2异步9.png)]

的参数传递给下一次的then

let thenable={
    then:function(resolve,reject){
        reslove(42);
    }
};
let p1=Promise.resolve(thenable);
p1.then(function(value){
    consloe.log(value);//42
})
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44691513/article/details/108454918
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢