JavaScript异步操作

lgrcyanny 7年前
   <p>JavaScript环境中产生异步操作的函数分为两大类: 计时函数 和 I/O函数 。如果要在应用中定义复杂的异步操作,就要使用者两类异步函数作为基本的构造快。本文没有对某个知识点细致展开,仅供思路参考。</p>    <h3>1. 计时函数</h3>    <p>先看一个经典的例子:</p>    <pre>  <code class="language-javascript">for(var i = 0;i < 5; i++){      setTimeout(function(){          console.log(i);      },5000);  }  console.log(i);</code></pre>    <p>结果输出什么?立马输出一个5,5秒钟过后连续输出5个5(呜呜呜呜呜~),搞懂为什么这样输出5,需要知道3件事:</p>    <ul>     <li> <p>这里只有一个 i 变量,作用域由 var 定义,不管i怎么变,都指向同一个内存区域;</p> </li>     <li> <p>循环结束后 i=== 5;</p> </li>     <li> <p>JavaScript事件处理器在线程空闲之前不会执行。</p> </li>    </ul>    <p>来,再来和我背一遍口诀: 先同步后异步最后回调 。在本例子中:</p>    <ul>     <li> <p>同步事件:for循环(不包含内部的setTimeout);外部的console.log(i);</p> </li>     <li> <p>异步事件:for循环内部的setTimeout。</p> </li>    </ul>    <p>先执行for循环,遇到setTimeout压入延迟事件队列,一边循环一边压入队列;for循环结束执行外部的console.log(i),此时i=5,故立即输出5,此时同步事件执行完毕,接下来开始执行异步事件setTimeout,5个setTimeout事件等待5秒同时在等待,所以5秒结束连续输出5个5。</p>    <p>再看一个例子:</p>    <pre>  <code class="language-javascript">var start = new Date;  setTimeout(function(){      var end = new Date;      console.log('time using:' + (end - start) + 'ms');  },5000);  while(new Date - start < 1000){};</code></pre>    <p>猜猜结果是什么?</p>    <p><img src="https://simg.open-open.com/show/8e832f47ce9652bbaac3754d06d67ae8.png"></p>    <p>调换一下时间:</p>    <pre>  <code class="language-javascript">var start = new Date;  setTimeout(function(){      var end = new Date;      console.log('time using:' + (end - start) + 'ms');  },2000);  while(new Date - start < 5000){};</code></pre>    <p><img src="https://simg.open-open.com/show/74c61b03fd962ba9a36143e3c2ce15a1.png"></p>    <p>这里唯一想说的是:像 setTimeout 或是 setInterval 并非是精确计时的, <a href="/misc/goto?guid=4959747809518879366" rel="nofollow,noindex">setTimeout与setInterval在不同浏览器下的差异</a> 。</p>    <h3>2. I/O函数</h3>    <p>这里的 I/O 是一个广义的概念,包括读写文件,GET或POST请求,异步读取值值函数等等。一个常见的读文件的操作:fs.js</p>    <pre>  <code class="language-javascript">var fs = require('fs');    fs.readFile('data.txt',function(err,data){      if(err){          return console.log(err);      }      console.log(data.toString())  });</code></pre>    <p>data.txt的内容:</p>    <pre>  <code class="language-javascript">hello  world  hello  async</code></pre>    <p>node fs.js:</p>    <p><img src="https://simg.open-open.com/show/2805ac8c74bf6bce8d57172ec7ccf899.png"></p>    <p>没毛病!设想一个场景,在另外一个函数,假设名字叫 panfen() ,里面有一堆代码,其中需要用到从data.txt文件读取的数据,常见的做法是把fs.readFile操作写在 panfen() 里面,处理data的操作都写在fs.readFile的callback里面,如果callback里面还要写数据呢?继续执行fs.writeFile在它的callback里面执行其他操作...</p>    <pre>  <code class="language-javascript">var fs = require('fs');    fs.readFile('data.txt',function(err,data){      if(err){          return console.log('failed to read data!');      }else{          fs.writeFile('./data_copy.txt', data, function(){              if(err){                  ...              }else{                  ...              }          });      }  });</code></pre>    <p>这就是 回调金字塔 。其弊端是操作强耦合、维护代价高。如何做到理想的异步呢?</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e625c579cd9cc3b0eb7e0c69a5fa4257.png"></p>    <p>灵机一动这样写:</p>    <pre>  <code class="language-javascript">var fs = require('fs');    var data = fs.readFile('data.txt',function(err,data){      return data ? data : err;  });  fs.writeFile('./data_copy.txt', data, function(){      ...  });</code></pre>    <p>然而,根据 先同步后异步最后回调 的法则,fs.writeFile里面使用到的data,肯定是 undefined</p>    <h3>3.Promise</h3>    <pre>  <code class="language-javascript">var fs = require('fs')    function myReadFile(filepath) {      return new Promise(function (resolve, reject) {          fs.readFile(filepath, function (err, data) {              data ? resolve(data) : reject(err);          });      });  }    function myWriteFile(filepath,data) {      return new Promise(function (resolve, reject) {          fs.writeFile(filepath,data,function (err) {              err ? reject(err) : resolve();          });      });  }    function test() {      myReadFile('data.txt').then(function(data){          myWriteFile('./data1.txt',data)      });  }  test();</code></pre>    <p>Promise的特点在于可以用then方法向下传递值。</p>    <h3>4. Generator</h3>    <p>4.1 function*</p>    <p>关于生成器函数,这里不作具体介绍,简单看一例子:</p>    <pre>  <code class="language-javascript">function* GenFunc(){      yield [1,2];      yield* [3,4];      yield "56";      yield* "78"    }  var gen = GenFunc();  console.log(gen.next());  console.log(gen.next());  console.log(gen.next());  console.log(gen.next());  console.log(gen.next());  console.log(gen.next());</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/25340b68ac3d8ccd41596d187f0c5ec6.png"></p>    <p>yield 和 yield* 的区别:</p>    <ul>     <li> <p>yield 只返回右值;</p> </li>     <li> <p>yield* 将函数委托给另一个生成器,或可迭代的对象(字符串、数组、arguments、Map、Set)</p> </li>    </ul>    <p>4.2 co</p>    <p>co是基于Generator的一个库:</p>    <pre>  <code class="language-javascript">var fs = require('fs');  var co = require('co');    function myReadFile(filepath){      return function(cb){          fs.readFile(filepath,cb);      };  }    function myWriteFile(filepath,data){      return function(cb){          fs.writeFile(filepath,data,cb);      };  }    co(function* GenFunc(){      var data = yield myReadFile('data.txt');      yield myWriteFile('data1.txt',data);  }).catch(function(err){      console.log(err);  });</code></pre>    <p>看起来是不是神清气爽?</p>    <p>4.3 Koa</p>    <p>Koa是基于Generator和co的web框架。</p>    <pre>  <code class="language-javascript">var koa = require('koa');  var app = koa();    app.use(function* (next){      console.log(1);      yield next;      console.log(3);  });  app.use(function* (){      console.log(2);      this.body = 'hello Koa!';  });  app.listen(8080);</code></pre>    <p>启动程序,流浪器输入 localhost:8080 ,看到页面有 hello Koa! ,控制台输入 1 2 3</p>    <h3>5.async/awit</h3>    <p>随着 Node 7 的发布,越来越多的人开始研究async/await,据说这是异步编程终级解决方案的 。个人觉得没有最好,只有更好。用这种方式改写:</p>    <pre>  <code class="language-javascript">'use strict'  var fs = require('fs')    function myReadFile(filepath) {      return new Promise(function (resolve, reject) {          fs.readFile(filepath, function (err, data) {              data ? resolve(data) : reject(err);          });      });  }    function myWriteFile(filepath,data) {      return new Promise(function (resolve, reject) {          fs.writeFile(filepath,data,function (err) {              err ? reject(err) : resolve();          });      });  }    async function test() {      const data = await myReadFile('data.txt');      await myWriteFile('./data1.txt',data);  }  test();</code></pre>    <p>这个例子还不足以体现async/awit的优势。</p>    <h3>6. 总结</h3>    <p>JavaScript的异步操作可能是区别其他语言比较大的一点,也是一个难点,不过也是很有趣的嘛。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000009131570</p>    <p> </p>