慕课网-揭秘一线互联网企业 前端JavaScript高级面试(双越)- 第4章 -异步
慕课网学习笔记-揭秘一线互联网企业 前端JavaScript高级面试(双越)- 第4章 -异步
begin
-
什么是单线程,和异步有什么关系
-
什么是event-loop
-
是否用过jQuery的 Deferred
-
Promise 的基本使用和原理
-
介绍一下async/await(和Promise的区别、联系)
-
总结一下当前JS解决异步的方案
单线程-Single-thread
什么是单线程,和异步的关系
-
单线程 - 只有一个线程,只能做一件事
-
原因 - 避免DOM渲染的冲突
-
解决方案 - 异步
// 循环运行期间,JS执行和DOM渲染暂时卡顿
var i,sum = 0;
for(i = 0; i < 100000000; i++) {
sum += i;
}
console.log(sum);
// alert 不处理,JS执行和DOM渲染暂时卡顿
console.log(1);
alert('hello')
console.log(2);
异步-async
原因 - 避免DOM渲染的冲突
-
浏览器需要渲染DOM
-
JS可以修改DOM结构
-
JS执行的时候,浏览器DOM渲染会暂停
-
两段JS也不能同时执行(都修改DOM就冲突了)
-
webworker支持多线程,但是不能访问DOM
解决方案 - 异步
console.log(1);
setTimeout(function(){
console.log(2)
}, 1000)
console.log(3);
console.log(4);
console.log(1);
$.agax({
url: 'xxx',
success: function(result){
console.log(result)
}
})
console.log(3);
console.log(4);
单线程-summary
-
单线程 - 只有一个线程,同时间只能做一件事
-
原因 - 避免DOM渲染的冲突
-
解决方案 - 异步
-
实现方法 - event loop
问题一: 没有按照书写方式执行,可读性差
问题二:callback中不容易模块化
event-loop
什么是事件轮询(循环)
-
事件轮询,JS实现异步的具体解决方案
-
同步代码,直接执行
-
异步函数先放在 异步队列 中
-
待同步函数执行完毕,轮询执行 异步队列 的函数
event-loop过程
-
主进程先执行完 同步函数
-
异步函数根据时间(是否延迟)依次放入进 异步队列 中
-
主进程一直监视异步队列中是否有函数,一有就立马拿到主进程执行
event-loop实例
事件轮询 - 实例分析1
setTimeout(function(){
console.log(100);
});
console.log(200);
主进程
console.log(200);
异步队列
function(){
console.log(100);
}
事件轮询 - 实例分析2
setTimeout(function(){
console.log(1);
},100);
setTimeout(function(){
console.log(2);
});
console.log(3);
主进程
console.log(3);
异步队列
// 立刻被放入
function(){
console.log(2);
}
// 100ms 之后被放入
function(){
console.log(1);
}
事件轮询 - 实例分析3
$.agax({
url: 'xxx',
success: function(result){
console.log('a')
}
})
setTimeout(function(){
console.log('b');
},100);
setTimeout(function(){
console.log('c');
});
console.log('d');
问:$.agax中的函数何时被放入异步队列中?
答:ajax加载完成(url请求成功)之后,具体跟网络快慢有关
所以结果可能有两种:
d,c,b,a 或者 d,c,a,b
jquery-deferred
jquery-deferred-介绍
jQuery 1.5 的变化
使用jQuery Deferred
初步引入 Promise 概念
-
无法改变JS异步和单线程的本质
-
只能从写法上杜绝callback 这种形式
-
它是一种语法糖形式,但是解耦了代码
-
很好的体现:开放封闭原则then
开放封闭原则: 对扩展开放,对修改封闭
jQuery1.5对AJAX的改变
jQuery1.5之前
var ajax = $.ajax({
url: 'data.json',
success: function() {
console.log('success 1')
console.log('success 2')
console.log('success 3')
},
error: function() {
console.log('error')
}
})
console.log(ajax) // 返回一个 XHR 对象
jQuery1.5之后
var ajax = $.ajax('data.json')
ajax.done(function() {
console.log('success 1')
})
.fail(function() {
console.log('error')
})
.done(function() {
console.log('success 2')
})
console.log(ajax) // 返回一个 deferred 对象
jQuery1.5之后的之后
var ajax = $.ajax('data.json')
ajax.then(function() {
console.log('success 1')
}, function() {
console.log('error 1')
})
.then(function() {
console.log('success 2')
}, function() {
console.log('error 2')
})
console.log(ajax) //
jquery-deferred-应用
-
总结,dtd的API可分为两类,用意不同
-
第一类:dtd.resolve / dtd.reject
-
第二类:dtd.then / dtd.done / dtd.fail
-
这两类应该分开,否则后果很严重
简单的异步操作代码
var wait = function() {
var task = function() {
console.log('执行完成')
}
setTimeout(task, 2000)
}
wait();
新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤
function waitHandle() {
var dtd = $.Deferred(); // 创建一个deferred对象
var wait = function(dtd) { // 要求传入一个deferred对象
var task = function() {
console.log('执行完成');
dtd.resolve(); // 表示异步任务已经完成
// dtd.reject() // 表示异步任务失败或出错
}
setTimeout(task, 2000);
return dtd; // 要求返回deferred 对象
}
// 注意,这里一定要有返回值
return wait(dtd)
}
var w = waitHandle();
w.then(function(){
console.log('ok 1')
},function() {
console.log('err 1')
}).then(function(){
console.log('ok 2')
},function() {
console.log('err 2')
})
// 还有 w.done w.fail
使用dtd.promise()
function waitHandle() {
var dtd = $.Deferred(); // 创建一个deferred对象
var wait = function(dtd) { // 要求传入一个deferred对象
var task = function() {
console.log('执行完成');
dtd.resolve(); // 表示异步任务已经完成
// dtd.reject() // 表示异步任务失败或出错
}
setTimeout(task, 2000);
return dtd.promise(); // 不是返回deferred 对象,而是返回promise
}
// 注意,这里一定要有返回值
return wait(dtd)
}
var w = waitHandle(); // w 接收的就是一个Promise对象
$.when(w)
.then(function(){
console.log('ok 1')
},function() {
console.log('err 1')
})
// w.reject() 执行这句话会直接报错
jquery-deferred - 总结
可以jQuery1.5 对AJAX的改变举例
说明如何简单的封装,使用Deferred
说明Promise和Defered的区别
promise
-
Promise 的基本使用和原理
-
基本语法回顾
-
异常捕获
-
多个串联
-
Promise.all 和 promise.race
-
promise标准
Promise 的出现
回调地狱
// 获取第一份数据
$.get(url1, (data1) => {
console.log(data1)
// 获取第二份数据
$.get(url2, (data2) => {
console.log(data2)
// 获取第三份数据
$.get(url3, (data3) => {
console.log(data3)
// 还可能获取更多的数据
})
})
})
Promise 解决 callback hell
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
})
}
// 使用
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then((data1) => {
console.log(data1)
return getData(url2)
}).then((data2) => {
console.log(data2)
return getData(url3)
}).then((data3) => {
console.log(data3)
}).catch(err => {
console.error(err)
})
promise-语法回顾
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
var img = document.createElement('img');
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject()
}
img.src = src
})
return promise
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src);
result.then(function(img){
console.log(img.width)
}, function(){
console.log('failed')
})
result.then(function(img){
console.log(img.height)
})
promise-捕获异常
一般来说,使用promise捕获异常,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法
。
原因:
-
catch能捕获到loadImg函数里面的语法错误,如throw new Error(‘自定义错误’);
-
catch也能捕获到reject(‘xx’)执行错误,需要传参
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
var img = document.createElement('img');
// throw new Error('自定义错误')
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject('图片加载失败')
}
img.src = src
})
return promise
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src);
// 规定:then只接受一个参数,最后统一用catch捕获异常
result.then(function(img){
console.log(img.width);
return img;
}).then(function(img){
console.log(img.height);
}).catch(function(ex) {
// 最后统一 catch
console.log(ex)
})
promise-串联
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
var img = document.createElement('img');
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject('图片加载失败')
}
img.src = src
})
return promise
}
var src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1);
var src2 = 'https://img1.mukewang.com/53f470450001411b01000100-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)
})
promise-all-race
// Promise.all接收一个promise对象的数组
// 待全部完成之后,统一执行 success
Promise.all([result1,result2]).then(datas => {
// 接收到 datas 是一个数组,依次包含多个 promise 返回的内容
console.log(datas[0])
console.log(datas[2])
})
// Promise.race 接收一个包含多个 promise 对象的数组
// 只要有一个完成,就执行success
Promise.race([result1,result2]).then(data => {
// data即最先执行完成的 promise 的返回值
console.log(data);
})
promise-标准总结
- 关于标准的闲谈
- 状态变化
- then
关于标准的闲谈
-
任何技术推广使用都需要一套标准来支撑
-
如html、js、CSS、HTTP等,无规矩不成方圆
-
任何不符合标准的东西,终将会被用户抛弃
-
不要挑战标准,不要自造标准
promise标准 - 状态变化
-
三种状态:pending、fulfilled、rejected
-
初始状态是pending
-
pending变为fulfilled,或者pending变为rejected
-
状态变化不可逆
then
-
promise实例必须实现这个方法
-
then()必须可以接收两个函数作为参数
-
then()返回的必须是一个Promise实例
promise-总结
-
基本语法
-
如何异常捕获(Error 和 reject 都要考虑)
-
多个串联-链式执行的好处
-
Promise.all 和 Promise.race
-
Promise标准 - 状态变化,then 函数
async-await
-
then只是将callback拆分了
-
async/await 是最直接的同步写法
const load = async function() {
const result1 = await loadImg(src1);
console.log(result1);
const result2 = await loadImg(src2);
console.log(result2);
}
load()
-
使用await,函数必须用async标识
-
await后面跟的是一个Promise实例
-
需要babel-polyfill
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<script>
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
var img = document.createElement('img');
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject('图片加载失败')
}
img.src = src
})
return promise
}
var src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
var src2 = 'https://img1.mukewang.com/53f470450001411b01000100-100-100.jpg'
const load = async function() {
const result1 = await loadImg(src1);
console.log(result1);
const result2 = await loadImg(src2);
console.log(result2);
}
load()
</script>
</body>
</html>
async-await-总结
-
基本语法
-
使用了Promise,并没有和Promise冲突
-
完全是同步的写法,再也没有回调函数
-
但是:改变不了JS单线程、异步的本质
异步-Summary
总结一下当前JS解决异步的方案
-
jQuery Deferred
-
Promise
-
Async/Await
-
Generator