JavaScript异步和单线程
例:
// 循环执行期间,JS 执行和DOM渲染暂时卡顿 var i, sum = 0; for(var i = 0; i < 1000000000; i++) { sum += 1; } console.log(sum); // alert 不处理,JS 执行和DOM渲染暂时卡顿 alert('hello'); console.log(2);
console.log(100) setTimeout(function() { console.log(200) //反正1000ms 之后执行 },1000) //先不管它,先让其他JS代码执行 console.log(300);
var ajax = $.ajax({ url: './data.json', success: function(result) { //ajax加载完才执行 console.log(result); //先不管它,先让其他JS代码执行 } }) console.log(ajax) //{readyState: 1, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …} console.log(200); //200
// jQuery1.5之前 var ajax = $.ajax({ url: './data.json', success: function(result) { console.log('success1'); console.log('success2'); console.log('success3'); }, error:function(){ console.log('error'); } }) console.log(ajax) //返回一个XHR对象
// jquery1.5之后 var ajax = $.ajax('./data.json'); ajax.done(function() { console.log('success1'); }) .fail(function() { console.log('error1'); }) .done(function() { console.log('success2') }) console.log(ajax); //返回一个deferred对象,可以进行链式操作 // 还可以使用很像promise写法 var ajax = $.ajax('./data.json'); ajax.then(function(){ console.log('success1'); },function(){ console.log('error1'); }).then(function(){ console.log('success2'); },function(){ console.log('error2'); })
2,jQuery Deferred应用
看一个简单的例子:
// 给出一段非常简单的异步操作代码,使用setTimeout函数 var wait = function() { var task = function() { console.log('执行完成'); // 1 // 2 // 3 } setTimeout(task, 1000) } wait() // 新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤
使用Deferred:(开放封闭原则)
function waitHandler() { // 定义 var dtd = $.Deferred(); //创建一个deferred对象 var wait = function(dtd) { //要求传入一个deferred对象 var task = function() { console.log('执行完成'); // 成功 dtd.resolve(); //表示异步任务已经完成 // 失败 // dtd.reject(); //表示异步任务失败或出错 } setTimeout(task, 1000) // wait返回 return dtd; //要求返回deferred对象 } // 最终返回 注意这里一定要有返回值 return wait(dtd); } var w = waitHandler(); w.then(function() { console.log('ok1'); }, function() { console.log('err1'); }).then(function() { console.log('ok2'); }, function() { console.log('err2'); }) // 还有 w.done w.fail
执行reject
function waitHandler() { // 定义 var dtd = $.Deferred(); //创建一个deferred对象 var wait = function(dtd) { //要求传入一个deferred对象 var task = function() { console.log('执行完成'); // 成功 // dtd.resolve(); //表示异步任务已经完成 // 失败 dtd.reject(); //表示异步任务失败或出错 } setTimeout(task, 1000) // wait返回 return dtd; //要求返回deferred对象 } // 最终返回 注意这里一定要有返回值 return wait(dtd); } var w = waitHandler(); w.then(function() { console.log('ok1'); }, function() { console.log('err1'); }) w.then(function() { //reject需要分开,不然执行顺序就错啦 console.log('ok2'); }, function() { console.log('err2'); })
这里注意一个原则:开放封闭原则
总结,dtd的API可分成两类,用意不同
第一类(主动执行):dtd.resolve dtd.reject
第二类(监听):dtd.then dtd.done dtd.fail
这两类应该分开,否则后果很严重
可在上面代码最后执行dtd.reject()试下结果
使用dtd.promise()
function waitHandler(){ var dtd = $.Deferred();//Deferred var wait = function(dtd) { var task = function(){ console.log('执行完成'); dtd.resolve(); } setTimeout(task,2000); return dtd.promise();//注意,这里返回的是promise,而不是直接返回deferred对象 } return wait(dtd); } var w = waitHandler();//经过上面的改动,w接收的就是一个promise对象 $.when(w) .then(function(){ console.log('ok1'); }) .then(function(){ console.log('ok1'); }) w.reject()//执行这句会报错 w.reject is not a function promise不能使用,只有监听的方法了,使用者只能监听,开发人员封装的时候才能用
说明promise和deferred区别: promise只能被动监听,不能主动执行
1,基本语法回顾(备注:现在高级浏览器基本都支持promise,如果有些不支持,可以在cdn上找,引入 bluebird )
<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>
function loadImg(src) { const promise = new Promise(function(resolve, reject) { //new Promise实例 var img = document.createElement('img'); img.src = src; img.onload = function() { resolve(img); } img.onerror = function() { reject('图片加载失败'); } }); return promise; //返回 Promise实例 } var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png"; var result = loadImg(src); //Promise实例 result.then(function(img) { //then监听结果,成功时执行resolve(), 失败时执行reject() console.log(1, img.width); //1 164 return img; }, function(img) { console.log('failed'); return img; }).then(function(img) { console.log(2, img.height) //2 36 })
2,异常捕获
// 规定:then只接受一个参数(成功的处理函数),最后统一用catch捕获异常 // var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png"; var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png";//写一个不存在的地址 var result = loadImg(src); result.then(function(img) { console.log(img.width); return img; }).then(function(img) { console.log(img.height); }).catch(function(ex) { // 最后统一用catch捕获异常 console.log(ex); //图片加载失败 (logo_1.png:1 GET https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png 404 (Not Found)) })
自定义错误:
function loadImg(src) { const promise = new Promise(function(resolve, reject) { //new Promise实例 var img = document.createElement('img'); // 模拟抛出错误 模拟语法错误,逻辑之外的bug throw new Error('自定义错误'); img.src = src; img.onload = function() { resolve(img); } img.onerror = function() { reject('图片加载失败'); } }); return promise; //返回 Promise实例 } // 规定:then只接受一个参数(成功的处理函数),最后统一用catch捕获异常 var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo.png"; // var src = "https://shared-https.ydstatic.com/dict/v2016/result/logo_1.png"; //写一个不存在的地址 var result = loadImg(src); result.then(function(img) { console.log(img.width); return img; }).then(function(img) { console.log(img.height); }).catch(function(ex) { // 最后统一用catch捕获异常 console.log(ex); })
3,多个串联
// 多个串联 图片只是用来模拟,实际可能更多的不是用图片,比如处理用户信息啥的,加载用户信息的时候,先拿到用户信息,再去处理其他信息等 var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png'; var result1 = loadImg(src1); var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg'; var result2 = loadImg(src2); // 链式操作 result1.then(function(img1) { console.log('第一个图片加载完成', img1.width); return result2; //重要!!! }).then(function(img2) { console.log('第二个图片加载完成', img2.width); }).catch(function(ex) { // 最后统一用catch捕获异常 console.log(ex); })
4,Promise.all (全部promise实例完成后) 和 Promise.race (只要有一个promise实例完成)
// Promise.all 接收一个包含多个promise实例的数组,待全部完成之后,统一执行success var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png'; var result1 = loadImg(src1); //pending var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg'; var result2 = loadImg(src2); Promise.all([result1, result2]).then(datas => { // 接收到的datas是一个数组,依次包含了多个promise返回的内容 console.log('all', datas[0]); //all <img src="https://shared-https.ydstatic.com/dict/v2016/result/logo.png"> console.log('all', datas[1]); //all <img src="https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg"> })
Promise.race (只要有一个promise实例完成)
// Promise.race 接收一个包含多个promise实例的数组 // 只要有一个完成,就执行success Promise.race([result1, result2]).then(data => { // data即最先执行完成的promise的返回值 console.log('race', data); //race <img src="https://shared-https.ydstatic.com/dict/v2016/result/logo.png"> })
promise标准
1,关于“标准”的闲谈
任何技术推广使用都需要一套标准来支撑
如html css js等,无规矩不成方圆
任何不符合标准的东西,终将会被用户抛弃
不要挑战标准,不要自造标准
2,状态变化
三种状态:pending fulfilled rejected
pending: 初始状态
fulfilled: 成功
rejected:失败
pending到fulfilled,或者pending到rejected,状态不可逆
3,then
Promise实例必须实现then方法
then()必须可以接收两个函数作为参数
then()返回的必须是一个Promise实例
如果then中没有返回promise实例,则默认返回的是上一个promise实例,如果返回了一个promise实例,那就默认为返回的promise实例
1,then只是将callback拆分了
// then只是将callback拆分了 var w = waitHandler(); w.then(function() { console.log('ok1'); }, function() { console.log('err1'); }).then(function() { console.log('ok2'); }, function() { console.log('err2'); })
2,async/await是最直接的同步写法
// async/await是最直接的同步写法 const load = async function() { const result1 = await loadImg(src1); console.log(result1); const result2 = await loadImg(src2); console.log(result2); } load()
完整的:
import 'babel-polyfill'; //引入babel-polyfill // async/await是最直接的同步写法 function loadImg(src) { const promise = new Promise(function(resolve, reject) { var img = document.createElement('img'); img.src = src; img.onload = function() { resolve(img); } img.onerror = function() { reject('图片加载失败'); } }); return promise; } var src1 = 'https://shared-https.ydstatic.com/dict/v2016/result/logo.png'; var src2 = 'https://img.mukewang.com/user/57b98e990001351004400440-100-100.jpg'; // async/await是最直接的同步写法 const load = async function() { //函数必须用async标识 const result1 = await loadImg(src1); //await后面跟的是一个Promise实例 console.log(result1); const result2 = await loadImg(src2); //await后面跟的是一个Promise实例 console.log(result2); } load()
用法:
要在函数体内使用await,函数必须用async标识
await后面跟的是一个Promise实例
需要安装babel-polyfill
npm i --save-dev babel-polyfill
3,总结
使用了Promise,并没有和Promise冲突
完全是同步的写法,再也没有回调函数
但是:改变不了js单线程,异步的本质
1,jquery Deferred
2,Promise
3,Async/await