目标
- 什么是单线程,和异步有什么关系
- 什么是 event-loop
- 是否用过 jQuery 的 Deferred
- 目前 js 解决异步的方案有哪些
- 如果只有 jq 如何解决异步
- Promise 的基本使用和原理
- async、await 的使用(ES2017)(和 Promise 的区别、联系)
什么是单线程,和异步的关系:
- 单线程 - 只有一个线程,只能做一件事
- 原因 - 避免 DOM 渲染的冲突
- 解决方案 - 异步
原因 - 避免 DOM 渲染冲突:
- 浏览器需要渲染 DOM
- JS 可以修改 DOM 结构
- JS 执行的时候,浏览器 DOM 渲染会暂停
- 两段 JS 也不能同时执行(都修改 DOM 就冲突了)
- webworker 支持多线程,但是
不能访问 DOM
解决方案 - 异步:
- 问题一:没按照书写方式执行,可读性差
- 问题二:callback 中不容易模块化
总结:
- 单线程就是同时只做一件事,两段 JS 不能同时执行
- 原因就是为了避免 DOM 渲染的冲突
- 异步是一种"无奈"的解决方案,虽然有很多问题
console.log('start')
var i, sum = 0;
for (i = 0; i < 1000000000; i++) {
sum++
}
console.log(sum)
console.log(100)
alert('hello world')
console.log(200)
console.log(100)
setTimeout(function () {
console.log(200)
}, 1000)
console.log(300)
console.log(400)
console.log(100)
$.ajax({
url: './data.json',
success: function (result) {
console.log(result)
}
})
console.log(300)
console.log(400)
- 单线程 - 同时间只能做一件事
- 原因 - 避免 DOM 渲染冲突
- 解决方案 - 异步
- 实现方式 - event loop
文字解释:
- 事件轮询,JS 实现异步的具体解决方案
- 同步代码,直接执行
- 异步函数先放在'异步队列'中
- 待同步函数执行完毕,轮询执行'异步队列'的函数
实例分析:
setTimeout(function () {
// 异步
console.log(100)
})
console.log(200)
// 主进程
console.log(200)
// 异步队列
function () {
console.log(100)
}
setTimeout(function () {
console.log(1)
}, 1000)
setTimeout(function () {
console.log(2)
})
console.log(3)
// 主进程
console.log(3)
// 异步队列
// 立即被放入
function () {
console.log(2)
}
// 1000ms之后被放入
function () {
console.log(1)
}
$.ajax({
url: './data.json',
success: function (res) {
console.log('a')
}
})
setTimeout(function () {
console.log('b')
}, 1000)
setTimeout(function () {
console.log('c')
})
console.log('d')
- jQ1.5 的变化
- 使用 jQ Deferred
不要以为所有的网站都是 vue 和 React 开发的
// jQ1.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) // 返回一个 Deferred 对象
// jQ1.5之后
var ajax = $.ajax('./data.json')
ajax.done(function () {
console.log('success a')
}).fail(function () {
console.log('fail 1')
}).done(function () {
console.log('success b')
}).fail(function () {
console.log('fail 2')
}).done(function () {
console.log('success c')
}).fail(function () {
console.log('fail 3')
})
console.log(ajax) // 返回一个 xhr 对象
// jQ1.5之后
// 很像 Promise 的写法
var ajax = $.ajax('./data.json')
ajax.then(function () {
console.log('success 100')
}, function () {
console.log('fail 100')
}).then(function () {
console.log('success 200')
}, function () {
console.log('fail 200')
}).then(function () {
console.log('success 300')
}, function () {
conso3le.log('fail 300')
})
jQ1.5 的变化:
- 无法改变 JS 异步和单线程的本质
- 只能从写法上杜绝 callback 这种形式
- 它是一种语法糖形式,但是解耦了代码
- 很好的体现:
开放封闭原则
使用 jQuery Deferred
:
var wait = function () {
var task = function () {
console.log('执行完成')
}
setTimeout(task, 2000)
}
wait()
// 已经封装好的
function waitHandle() {
var dtd = $.Deferred() // 创建一个 deffered 对象
var wait = function (dtd) { // 要求传入一个 deffered 对象
var task = function () {
console.log('执行完成')
dtd.resolve() // 表示异步任务已经完成
// dtd.reject() // 表示异步任务失败或出错
}
setTimeout(task, 1000)
// wait 返回
return dtd; // 要求返回 deffered 对象
}
// 最终返回,这里一点要有返回值
return wait(dtd)
}
// 使用1
var w = waitHandle();
// w.reject() 可以主动调用
w.then(function () {
console.log('ok 1')
}, function () {
console.log('err 1')
}).then(function () {
console.log('ok 2')
}, function () {
console.log('err 2')
})
var w = waitHandle()
w.done(function () {
console.log('success a')
}).fail(function () {
console.log('fail 1')
}).done(function () {
console.log('success b')
}).fail(function () {
console.log('fail 2')
})
总结:
- 总结,dtd 的 API 可分成两类,用意不同
- 第一类: dtd.resolve dtd.reject
- 第二类: dtd.then dtd.done dtd.fail
- 这两类应该分开,否则后果很严重!
- 可以在上面代码最后执行 dtd.reject() 试一下后果
使用 dtd.promise():promise 对象只能监听,不能修改(即不能主动调用 reject())
function waitHandle() {
var dtd = $.Deferred()
var wait = function (dtd) {
var task = function () {
console.log('执行完成')
dtd.resolve()
}
setTimeout(task, 1000)
// wait 返回
return dtd.promise() // 注意返回的是 promise 对象
}
// 最终返回
return wait(dtd)
}
var w = waitHandle() // promise 对象
$.when(w).then(function () {
console.log('ok 1')
}, function () {
console.log('err 1')
})
// 对 w.reject 会直接报错,这里不可以执行 只能被动监听,不能主动修改
总结:
- 可以 jQuery 1.5 对 ajax 的改变举例
- 说明如何简单的封装、使用 Deferred
- 说明 promise 和 Deferred 的区别
要想深入理解它,就需要知道它的身世今生
- 基本语法
- 异常捕获
- 多个串联
- Promise.all \ Promise.race
- Promise 标准
基本语法:
var src1 = 'https://www.baidu.com/img/540%20258_c622d80176946df7f7b8d1997edf57d4.gif'
function loadImg(src) {
var 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 result = loadImg(src1)
result.then(function (img) {
console.log(1, img.width)
return img
}, function () {
console.log('error 1')
}).then(function (img) {
console.log(2, img.height)
})
异常捕获:规定,then 只接受一个参数,最后统一用 catch 捕获异常
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img
}).then(function (img) {
console.log(2, img.height)
}).catch(function (ex) {
// 统一捕获异常
console.log(ex)
})
多个串联:
var src2 = 'https://www.baidu.com/img/540%20258_c622d80176946df7f7b8d1997edf57d4.gif'
var result1 = loadImg(src1)
var result2 = loadImg(src2)
result1.then(function (img1) {
console.log('第一个图片加载完成', img1.width)
return result2 // 重要!!! 返回一个 promise 实例
}).then(function (img2) {
console.log('第二个图片加载完成', img2.width)
}).catch(function (ex) {
// 统一捕获异常
console.log(ex)
})
promise-all-race:
var src2 = 'https://www.baidu.com/img/540%20258_c622d80176946df7f7b8d1997edf57d4.gif'
var result1 = loadImg(src1)
var result2 = loadImg(src2)
// Promise.all 接收一个 Promise 对象的数组
// 待全部完成之后,统一 success
Promise.all([result1, result2]).then(function (datas) {
// 收到的 datas 是一个数组,依次包含了多个 Promise 返回的内容
console.log('all', datas[0])
console.log('all', datas[1])
})
// Promise.race 接收一个包含多个 Promise 对象的数组
// 只要有一个完成,就执行 success
Promise.race([result1, result2]).then(function (data) {
// data 是最先执行完成的 Promise 的返回值
console.log('race', data)
})
Promise 标准:
- 关于 "标准" 的闲谈
- 状态变化
- then
关于 "标准" 的闲谈:
- 任何技术推广使用都需要一套标准来支撑
- 如 html js css http 等,无规矩不成方圆
- 任何不符合标准的东西,终将会被用户抛弃
- 不要挑战标准,不要自造标准
Promise 标准 - 状态变化:
- 三种状态:pending fulfilled rejected
- 初始状态是 pending
- pending 变为 fulfilled ,或者 pending 变为 rejected
- 状态变化不可逆
Promise 标准 - then:
- Promise 实例必须实现 then 这个方法
- then() 必须可以接收两个函数作为参数
- then() 返回的必须是一个 Promise 实例
总结:
- 基本语法
- 如何异常捕获(Error 和 reject 都要考虑)
- 多个串联 - 链式执行的好处
- Promise.all 和 Promise.race
- Promise 标准 - 状态变化,then 函数
- then 只是将 callback 拆分了
- async/await 是最直接的同步写法
- 语法
then 只是将 callback 拆分了:
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')
})
最直接的同步写法:
import 'babel-polyfill'
function loadImg(src) {
var 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
}
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
总结:
- 基本语法
- 使用了 Promise ,并没有和 Promise 冲突
- 完全是同步的写法,再也没有回调函数
- 但是:改变不了 JS 单线程、异步的本质
目标:
- 什么是单线程,和异步的关系
- 什么是 event-loop
- 是否用过 jquery 的 Deferred
- 目前 js 解决异步的方案有哪些
- 如果只有 jq 如何解决异步
- Promise 的基本使用和原理
- async、wait 的使用(ES2017)(和 Promise 的区别、联系)
什么是单线程,和异步的关系:
- 单线程就是同时只做一件事,两段 JS 不能同时执行
- 原因就是为了避免 DOM 渲染的冲突
- 异步是一种"无奈"的解决方案,虽然有很多问题
什么是 event-loop:
- 事件轮询,JS 异步的解决方案
- 什么是异步队列,何时被放入异步队列
- 轮询的过程
jQuery Deferred:
- 可以 jQuery 1.5 对 ajax 的改变举例
- 说明如何简单的封装、使用 Deferred
- 说明 promise 和 Deferred 的区别
Promise 使用和原理:
- 基本语法
- 如何异常捕获
- 多个串联 - 链式执行的好处
- Promise.all 和 Promise.race
- Promise 标准 - 状态变化,then 函数
async/await:
- 基本语法
- 使用了 Promise ,并没有和 Promise 冲突
- 完全是同步的写法,再也没有回调函数
- 但是:改变不了 JS 单线程、异步的本质
当前异步的解决方案:
- jQuery Deferred
- Promise
- Async/Await
- Generator
关于 Generator:
- 原理比较复杂
- 不是异步的直接替代方式
- 有更好更简洁的解决方案 async/await
- koa 也早已"弃暗投明"