平时开发经常会用到js异步编程,由于前端展示页面都是基于网络机顶盒(IPTV的一般性能不太好,OTT较好),目前公司主要采取的异步编程的方式有setTimeout、setInterval、requestAnimationFrame、ajax,为什么会用到异步呢,就拿业务来说,若前端全部采取同步的方式,那加载图片、生成dom、网络数据请求都会大大增加页面渲染时长。

JS 是单线程运行的,这意味着两段代码不能同时运行,而是必须逐步地运行,所以在同步代码执行过程中,异步代码是不执行的。只有等同步代码执行结束后,异步代码才会被添加到事件队列中。

这里就涉及到执行栈和任务队列:

同步代码是依次存放在执行栈中,遵循LIFO原则;

异步代码存放在任务队列中,任务队列又分宏任务和微任务(微任务执行优先级高于宏任务),遵循FIFO原则;

请看下面代码执行的顺序(可以先思考一下看看与正确输出顺序是否一致)

  1. 1 function foo(){
  2. 2 console.log('start...');
  3. 3 return bar();
  4. 4 }
  5. 5 function bar(){
  6. 6 console.log('bar...');
  7. 7 }
  8. 8 //这里采用ES6的箭头函数、Promise函数
  9. 9 var promise = new Promise(function(resolve,reject){
  10. 10 console.log('promise...');
  11. 11 resolve();
  12. 12 });
  13. 13 promise.then(()=>console.log('promise resolve...'));
  14. 14 setTimeout(()=>console.log('timeout...'),0);
  15. 15 foo()
  16. 16 console.log('end...');

 

请看答案


 

 

promise…
start…
bar…
end…
promise resolve…
timeout…

这里分析一下(大家不要纠结任务队列的叫法,本人说明的异步微任务、异步宏任务暂无根据,理解即可,请勿深究):

程序正式开始执行是从9行初始化promise对象开始,首先打印promise…

然后往下执行发现是promise.then回调函数,此为异步微任务,放入任务队列中,等待同步任务执行完才能执行

再往下执行是timeout定时器,此为异步宏任务,也放入任务队列中,等待同步任务执行完、异步微任务才能执行

再往下是foo方法,此为同步任务,借用网络流行的一句话 “JavaScript中的函数是一等公民”,打印日志start…后回调执行bar方法,到这里就有两个执行栈了(依次将foo、bar放入栈中,bar执行完就弹出栈,foo依次弹出)

关于并发模型和Event Loop 请看MDN(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop)

关于异步编程的方式,常用的定时器、ajax、Promise、Generator、async/await,详细介绍如下:

这里拿setTimeout来举例

简单的时钟

  1. 1 (function(){
  2. 2 var div = document.createElement('div'),timer;
  3. 3 document.body.appendChild(div);
  4. 4 //同步代码,5s后执行异步代码块显示时钟
  5. 5 //doSomething()
  6. 6 setTimeout(function(){
  7. 7 execFn();
  8. 8 },5000);
  9. 9 function timeChange(callback){
  10. 10 div.innerHTML = '当前时间:'+getCurrentTime();
  11. 11 if(new Date().getSeconds() %5 === 0){
  12. 12 //当前秒数是5的倍数关闭定时器
  13. 13 clearTimeout(timer);
  14. 14 //doSomething...
  15. 15 console.log(timer);
  16. 16 timer = setTimeout(execFn,100);
  17. 17 }else{
  18. 18 clearTimeout(timer);
  19. 19 execFn();
  20. 20 }
  21. 21 }
  22. 22 function execFn(){
  23. 23 timer1 = window.setTimeout(timeChange,1000);
  24. 24 }
  25. 25 function getCurrentTime(){
  26. 26 var d = new Date();
  27. 27 return d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()+' '+d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()+' 星期'+getWeek();
  28. 28 }
  29. 29 function getWeek(){
  30. 30 var d = new Date();
  31. 31 var week;
  32. 32 switch(d.getDay()){
  33. 33 case(4):week='四';break;
  34. 34 //省略
  35. 35 default:week='*';break;
  36. 36 }
  37. 37 return week;
  38. 38 }
  39. 39 })();

正常的逻辑代码肯定要复杂的多,但是利用setTimeou编写异步代码的逻辑大致上是这么处理的。

看下面的例子

大家是否有疑问,为啥不是先输出2再输出1

setTimeout与setInterval执行的间隔时间为4~5ms

下面看setInterval代码

计数count输出为252,所以执行的间隔时间约为4ms

看看caniuser支持的情况

看这趋势除了opera外其他浏览器以后都支持requestAnimationFrame方法

平时业务中也看到公司同事封装了requestAnimationFrame方法。如果碰到某些版本的浏览器不支持此方法,则需要重写,requestAnimationFrame其实与防抖节流实现的原理有些相似,请看代码

  1. 1 var vendors = ['webkit', 'moz'];
  2. 2 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
  3. 3 var vp = vendors[i];
  4. 4 window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
  5. 5 }
  6. 6 if(!window.requestAnimationFrame){
  7. 7 var lastTime = 0;
  8. 8 window.requestAnimationFrame = function(callback){
  9. 9 var now = new Date().getTime();
  10. 10 var nextTime = Math.max(lastTime + 16, now);//浏览器渲染的间隔时间大约16ms
  11. 11 return window.setTimeout(function(){
  12. 12 lastTime = nextTime;
  13. 13 callback();
  14. 14 },nextTime - now);
  15. 15 };
  16. 16 }

有兴趣的同学可以看看这位大神的杰作

https://codepen.io/caryforchristine/pen/oMQMQz

直接看一个简单的ajax异步处理代码 

  1. 1 (function(){
  2. 2 var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
  3. 3 var url = "authorInfo.json";
  4. 4 xmlhttp.onreadystatechange = function(){
  5. 5 if(xmlhttp.readyState==4){
  6. 6 if(xmlhttp.status==200){
  7. 7 console.log(xmlhttp.response);
  8. 8 //异步获取数据后再doSomething
  9. 9 }
  10. 10 }
  11. 11 }
  12. 12 xmlhttp.open('GET',url,true);
  13. 13 xmlhttp.send(null);
  14. 14 })();

chrome打印日志

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象

简单的读取文件实例

  1. 1 var fs = require('fs')
  2. 2 var read = function (filename){
  3. 3 var promise = new Promise(function(resolve, reject){
  4. 4 fs.readFile(filename, 'utf8', function(err, data){
  5. 5 if (err){
  6. 6 reject(err);
  7. 7 }
  8. 8 resolve(data);
  9. 9 })
  10. 10 });
  11. 11 return promise;
  12. 12 }
  13. 13 read('authorInfo.json')
  14. 14 .then(function(data){
  15. 15 console.log(data);
  16. 16 return read('not_exist_file');
  17. 17 })
  18. 18 .then(function(data){
  19. 19 console.log(data);
  20. 20 })
  21. 21 .catch(function(err){
  22. 22 console.log("error caught: " + err);
  23. 23 })
  24. 24 .then(function(data){
  25. 25 console.log("completed");
  26. 26 })

用node运行结果如下:

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject(函数)

当状态由pending变成resolved执行resolve(),变成rejected则执行reject(),当promise实例生成时可以用then指定回调

then(function success(){},function fail(){}),此方法还是会返回一个新的promise对象,所以可以进行链式调用

有关Promise包括下文要提到的Generator请看阮老师博客

本人在第一次接触Generator的时候觉得特神奇,毕竟之前从来没有想过函数会断点执行(在下描述不准确,勿喷),也就是说函数执行一部分可以停下来处理另外的代码块,然后再回到暂停处继续执行。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

由此可见Generator返回的是一个遍历器对象,可以用for of(ES6新特性,主要是针对具有Symbol.iterator属性的对象,包括数组,set,map,类数组等等)进行遍历,

Generator语法 function* name(){},一般*和函数名中间有个空格,函数体内可通过yield关键字修饰,需要注意的是,yield后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。大家是否会觉得Generator要手动执行next方法过于麻烦呢,接下来介绍当前js对异步的终极解决方案

async和await是ES 7中的新语法,新到连ES 6都不支持。

可以利用babel转换

在线转换地址:https://babeljs.io/  ,也可以自己安装babel-cli进行转换

  1. 1 const fs = require('fs');
  2. 2 const utils = require('util');
  3. 3 const readFile = utils.promisify(fs.readFile);
  4. 4 async function readJsonFile() {
  5. 5 try {
  6. 6 const file1 = await readFile('zh_cn.json');
  7. 7 const file2 = await readFile('authorInfo.json');
  8. 8 console.log(file1.toString(),file2.toString());
  9. 9 } catch (e) {
  10. 10 console.log('出错啦');
  11. 11 }
  12. 12
  13. 13 }
  14. 14 readJsonFile();

 

可以看到异步依次读取两个文件,如果利用Generator的话需要手动执行next,async/await实现了自动化

 

写的不周到或者有错误的地方欢迎各位大神及时指出。

欢迎纠错~

 

 

 

 

 

 

posted on 2018-08-10 18:04 caryForJava 阅读() 评论() 编辑 收藏

版权声明:本文为ccylovehs原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ccylovehs/p/9452361.html