【学习笔记】JavaScript异步编程
一、基本概念
JavaScript代码执行:单线程
- 优点:安全、简单
- 缺点:耗时任务阻塞执行
JavaScript任务执行模式:
- 同步模式(Synchronous):代码按顺序依次排队执行
- 异步模式(Asynchronous):不会等待这个任务的结束才开始下一个任务
异步编程主要内容:
- 同步模式和异步模式——表象差异 和 存在意义
- JS的单线程如何实现的异步模式——实现循环 和 消息队列
- 异步编程的几种方式
- Promise异步编程方案、宏任务/微任务队列
- Generator异步方案、Async/Await语法糖
所有异步编程方案的根基:回调函数(调用者调用,执行者执行)
异步编程方案:
- 传统回调(回调地狱问题)
- Promise对象
- 三种状态(状态不可逆):
- Pending
- Rejected->onRejected
- Fulfilled->onFulfilled
- then
- Promise 对象的 then 方法会返回一个全新的 Promise
- 如果回调中返回的是 Promise,那后面 then 方法的回调会等待它的结東
- 前面 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
- 对象后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调
- 异常处理:
- then方法第二个参
- catch方法:
- promise上任何一个异常都会被一直向后传递直到捕获
- 全局对象注册:unhandledrejection
- 并行执行
- Promise.all(所有成功才成功)
- Promise.race(只会等待第一个结束的任务)
微任务 vs 宏任务
- 微任务(临时任务):直接在当前任务结束后立即执行(Promise、MutationObserver、process.nextTick)
- 宏任务:回调队列中的任务
ES2015:Generator(生成器函数)
- function * handle() {} 创建生成器函数
- yield、next控制代码执行
- done判断是否执行完yield
ES2015:Async(语言层面的异步编程标准)
二、相关代码
同步执行
console.log('global begin')
function bar() {
console.log('bar task')
}
function foo() {
console.log('foo task')
bar()
}
foo()
console.log('global end')
/*
调用栈:js在执行引擎当中维护了一个正在工作的工作表
调用栈执行步骤:
1. 在调用栈中压入一个匿名调用(可理解为把全部的代码放入一个匿名函数种执行)
2. console.log('global begin') 压入调用栈 执行并弹出
3. foo函数压入调用栈并执行
4. console.log('foo task') 压入执行并弹出
5. bar函数压入调用栈并执行
6. console.log('bar task') 压入执行并弹出
7. bar函数执行完成弹出
8. foo函数执行完成弹出
9. console.log('global end') 压入调用栈 执行并弹出
*/
异步执行
console.log('global begin')
setTimeout(function timer1(){
console.log('timer1 invoke')
},1800)
setTimeout(function timer1(){
console.log('timer2 invoke')
setTimeout(function inner(){
console.log('inner invoke')
},1000)
},1000)
console.log('global end')
/*
调用栈
内部API环境(例如web APIs)
事件循环(event loop):负责监听调用栈和消息队列
消息队列/回调队列
调用栈执行步骤:
1. 在调用栈中压入一个匿名调用(可理解为把全部的代码放入一个匿名函数种执行)
2. console.log('global begin') 压入调用栈 执行并弹出
3. setTimeout(timer1)压栈,在内部API环境中为timer1开启一个倒计时器,当倒计时结束让入消息队列
4. setTimeout(timer2)压栈,在内部API环境中为timer2开启一个倒计时器,当倒计时结束让入消息队列
5. console.log('global end') 压入调用栈 执行并弹出
6. 清空调用栈
7. event loop监听到调用栈为空开始从消息队列中取出第一个timer2回调函数压栈
8. console.log('timer2 invoke') 压入调用栈 执行并弹出
9. setTimeout(inner)压栈,在内部API环境中为inner开启一个倒计时器,当倒计时结束让入消息队列
10. event loop消息队列中取出timer1回调函数压栈
11. console.log('timer1 invoke') 压入调用栈 执行并弹出
12. timer1执行完毕弹出
13. event loop消息队列中取出inner回调函数压栈
14. console.log('inner invoke') 压入调用栈 执行并弹出
15. inner执行完毕弹出
*/
回调函数
function foo(callback) {
setTimeout(function () {
callback()
},3000)
}
foo(function() {
console.log('这就是一个回调函数')
})
Promise 基本示例
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现承诺”
resolve(100) // 承诺达成 状态不可逆
// reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function(value) {
console.log('resolved', value)
}, function(error) {
console.log('rejected', error)
})
Promise封装ajax
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function() {
if(this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('xxxx').then(function(res) {
console.log(res)
}, function(error) {
console.log(error)
})
Promise链式调用
ajax('xxxx')
.then(function(res) {
console.log('111')
return ajax('sssss')
})
.then(function(res) {
console.log('222')
console.log(res) // ssss接口请求的返回
})
.then(function(res) {
console.log('333')
})
// 每一个then方法实际上都是在为上一个then返回的promise对象添加状态明确过后的回调
Promise异常处理
// then捕获
ajax('xxxx')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
}, function onRejected(error) {
console.log('onRejected', res)
})
// catch捕获
ajax('xxxx')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.catch(function onRejected(error) {
console.log('onRejected', error)
})
// catch捕获等同于then如下写法捕获错误
ajax('xxxx')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.then(undefined, function onRejected(error) {
console.log('onRejected', error)
})
全局异常捕获
window.addEventListener('undandledrejection', event => {
const { reason, promise } = event
console.log(reason, promise)
event.preventDefault
},false)
// node
process.on('undandledrejection', (reason, promise) => {
console.log(reason, promise)
// reason:Promise 失败原因,一般是一个错误对象
// promise:出现异常的 Promise 对象
})
Promise并行执行
var promise = Promise.all([ajax('xxx'),ajax('ssss')])
promise.then(function(values) {
console.log(values)
}).catch(function(error) {
console.log(error)
})
const request = ajax('wqeqweqw')
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([
request,
timeout
])
.then(value => {
console.log(value)
})
.catch(error => {
console.log(error)
})
Generator(生成器函数)
function * foo() {
console.log('start')
try {
const res = yield 'foo'
console.log(res)
} catch(e) {
console.log(e)
}
}
const generator = foo()
const result = generator.next()
console.log(result)
// generator.next('bar')
generator.throw(new Error('Generator error'))
Generator配合Promise
function * main() {
try {
const users = yield ajax('/api/user.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
} catch(e) {
console.log(e)
}
}
function co (generator) {
const g = generator()
const result = g.next()
function handleResult(result) {
if(result.done) return
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
Async函数
async function main() {
try {
const users = await ajax('/api/user.json')
console.log(users)
const posts = await ajax('/api/posts.json')
console.log(posts)
} catch(e) {
console.log(e)
}
}
const promise = main()
promise.then(() => {
console.log('all completed')
})