内容概要
- 同步模式与异步模式
- 事件循环与消息队列
- 异步编程的几种方式
- Promise异步方案、宏任务/ 微任务队列
- Generator异步方案、Async/ Await 语法糖
单线程模式
JS执行环境中负责执行代码的线程只有一个
原因:
JavaScript 采用单线程模式工作, 最早JavaScript 是一门运行在浏览器端的脚本语言, 目的是为了实现页面的动态交互, 实现交互的核心是DOM操作, DOM操作决定了JavaScript必须以单线程模式运行, 否则会出现很复杂的线程同步问题
优点:
更安全 更简单
缺点:
如果遇到一个特别耗时的任务, 后面的任务要排队等待任务的结束, 导致整个程序的执行会被拖延, 出现假死的情况
解决:
将任务执行模式分为: 同步模式 和 异步模式
同步模式(Synchronous)
同步模式就是指代码中的任务依次执行, 后一个任务必须等前一个任务执行结束才能执行。程序的执行顺序和代码的编写顺序完全一致。
异步模式(Asynchronous)
异步模式的API不会等待这个任务的结束才开始下一个任务, 对于耗时操作, 开启过后就立即往后执行下一个任务, 后续逻辑一般通过回调函数的方式定义
优点: 使得单线程的JavaScript语言可以同时处理大量耗时任务
缺点: 代码的执行顺序混乱
回调函数(所有异步编程方案的根基)
由调用者定义, 交给执行者执行的函数就被称为回调函数
function foo(callback){
setTimeout(function(){
callback()
}, 3000)
}
foo(function(){
console.log('这就是一个回调函数')
console.log('调用者定义这个函数,执行者执行这个函数')
console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})
Promise(一种更优的异步编程统一方案)
直接使用传统回调方式去完成复杂的异步流程, 无法避免大量的函数嵌套, 容易形成回调地狱, CommonJS社区提出了Promise的规范, 在ES2015中被标准化, 称为语言规范
//Promise 基本示例
const promise = new Promise(function(resolve, reject){
//这里用于"兑现"承诺
resolve(100) //承诺达成
//reject(new Error('promise rejected')) //承诺失败
})
//注意:即便promise中没有任何异步操作, then方法后指定的回调函数仍会进入回调队列排队
promise.then(function(value){
console.log('resolved',value)
},function(error){
comsole.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('/api/foo.json').then(res=>{},err=>{})
借用 Promise then 方法链式调用的特点, 尽可能保证异步任务的扁平化
- Promise 对象的 then 方法会返回一个全新的 Promise 对象
- 后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调
- 前面的 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
- 如果回调中返回的是 Prormise, 那后面 then 方法的回调会等待它的结束
catch与then中失败回调的差别: catch是给整个promise链注册的失败回调, promise链条上任何一个失败异常会一直向后传递, 直至被捕获, then中失败只能捕获上一个promise的异常
//全局对象上注册unhandledrejection事件,去处理代码中没有被手动捕获的异常
window.addEventListener('unhandledrejection', event =>{
const { reason, promise } = event
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
event.preventDefault()
}, false)
//在全局捕获不推荐, 应该在代码中明确每一个可能捕获的异常
process.on('unhandledRejection', (reason, promise)=>{
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
})
promise静态对象
通过promise.resolve包装一个promise对象, 得到的是原本的promise
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2) //true
promise并行执行
Promise.all() 等待所有任务运行结束
Promise.all([
ajax('/api/users.json'),
ajax('/api/posts.json')
]).then(values=>{
//values是一个数组, 返回的是每一个异步返回的结果
}).catch(err=>{
//err 只要有一个失败就会被捕捉
})
ajax('/api/urls.json')
.then(value=>{
//把所有的接口封装在promise.all中
const urls = Object.values(value)
const tasks = url.map(url => ajax(url))
return Promise.all(tasks)
})
.then(values=>{
console.log(values)
})
Promise.race() 只会等待第一个结束的任务
const request = ajax('api/posts.json')
const timeout = new Promise((resolve, reject) =>{
setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([
request,
timeout
])
.then(value=>{
console.log(value)
})
.catch(err=>{
console.log(err)
})
Promise 执行时序
回调队列中的任务称之为 宏任务, 宏任务执行过程中临时加上一些临时需求, 可以选择作为一个新的宏任务进到队列中排队
微任务: 直接在当前任务结束过后立即执行,提高整体的响应能力, Promise 的回调会作为微任务执行
//微任务
console.log('global start')
setTimeout((=>{
console.log('setTimeout')
},0)
Promise.resolve()
.then(()=>{
console.log('promise')
})
console.log('global end')
//global start
//global end
//promise
//promise2
//promise3
//setTimeout
目前绝大多数异步调用都是作为宏任务执行, Promise & MutationObserver & processnextTick 都是微任务
Generator 异步方案
function *foo(){
console.log('start')
try {
const res = yield 'foo' //可以用yield向外返回值
console.log(res) //bar
}catch(err){
console.log(err)
}
}
const generator = foo() //得到一个生成器对象
const result = generator.next() //开始执行
console.log(result) //拿到返回值 {value:"foo", done:false} done表示是否执行完了
generator.next('bar') //像yield的左边传参
generator.throw(new Error('Generator error')) //向生成器内部抛出异常