js是单线程原因
● 原因-避免DOM渲染的冲突
● 浏览器需要渲染DOM
● JS可以修改DOM结构
● JS执行的时候,浏览器DOM渲染会暂停
● 两段JS也不能同时执行(都修改DOM就冲突了)
● web worker支持多线程,但是不能访问DOM
1. 回调函数
回调函数是所有异步编程方法的根基
function foo(callback){
setTimeout(function(){
callback()
}, 3000)
}
foo(function(){
console.log('这就是一个回调函数')
console.log('调用者定义这个函数,执行者执行这个函数')
console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
}
2. 异步调用线程
JS是单线程的,但是浏览器的API是多线程的,例如当setTimeout,当JS执行主线程代码时,倒计时器会有一个单独的线程去计时
来看下图,看下JS在有异步函数时是怎样工作的

3. Promise
即使Promise中没有异步操作,then中的函数也会进入回调队列排队
Promise本质上也是使用回调函数,定义的异步任务结束后再执行所需要执行的任务
3.1 Promise特点
- Promise对象的then方法会返回一个全新的Promise对象
- 后面的then方法就是在为上一个then返回的Promise注册回调
- 前面then方法中回调函数的返回值会作为后面then方法回调的参数
- 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束(后面的then方法相当于该Promise的回调)
promise有值穿透的情况,then
或者 catch
的参数期望是函数,若传入非函数则会发生值穿透。来看如下代码:
Promise.resolve(1)
.then(2)
.then(3)
.then(console.log) // 输出为1
3.2 Promise异常捕获
Promise捕获有两种方式,第一种是在then中传入一个失败函数,这种方式只能捕获到本次Promise异常
then(function success() {}, function fail() {})
第二种是通过catch
catch只能捕获到它的上一个then里的异常,但是因为Promise是链式传递的,不管哪一个then中出现异常,最终都会传递到最后一个then中,因此所有都可以用catch捕获
.then()
.then()
.catch()
3.3 node中注册unhandledRejection
方法
process.on('unhandledRejection', (reason, promise) => {
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
})
3.4 Promise.resolve()
通过Promise.resolve()可以将第三方Promise对象转为原生Promise
Promise.resolve({
then: function (onFulfilled, onRejected) {
onFulfilled('foo')
}
}).then((value) => {
console.log(value)
})
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true
3.5 Promise执行顺序问题
catch并不会阻断then的调用
Promise.resolve(2)
.then((res) => {
console.log(res)
return Promise.resolve(3)
})
.catch(e => console.log(e))
.then((res) => {
console.log(res)
})
// 2 3
3.6 使用reduce实现promise.all()效果
const axios = require('axios')
const promises = [
() => axios('https://api.github.com'),
() => axios('https://api.github.com/users')
]
const p = promises.reduce((prev, current) => {
return prev.then(() => current())
}, Promise.resolve())
console.log(p.then(res => console.log(res)))
为了防止有promise出错导致整个promise失败,建议在每个promise后加catch
const promise = Promise.allSettled(urls.map(item => axios(item).catch(e => e)))
4. 宏任务、微任务
回调队列中的任务称为宏任务,新的宏任务需要开启下一轮,而微任务是排在本轮任务末尾,在当前任务结束后立即执行
宏任务:script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering等
微任务:Promise、MutationObserver、process.nextTick
Event Loop: 用于等待和发送消息和事件,负责主线程与其他进程(主要是各种I/O操作)的通信。
具体步骤:
- 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
- 在此期间WebAPIs完成这个事件,把回调函数放入CallbackQueue中等待;
- 当执行栈为空时,检查微任务(microtask)队列,将可执行的微任务全部执行。
- Event Loop把Callback Queue中的一个任务放入Stack中,回到第1步。
来看一道题:
setTimeout(() => console.log('A'), 0) // fn1
setTimeout(() => console.log('B'), 1000) // fn2
Promise.resolve()
.then(() => { // fn3
setTimeout(() => console.log('C'), 0) // fn4
setTimeout(() => console.log('D'), 1000) // fn5
console.log('E')
Promise.resolve()
.then(() => console.log('F')) // fn6
})
.then(() => console.log('G'))
setTimeout(() => console.log('H'), 0) // fn7
setTimeout(() => console.log('I'), 1000) // fn8
5. Generator
5.1 使用Generator实现异步请求
function* main() {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
const g = main()
function handleResult(result) {
if (result.done) return // 生成器函数结束
result.value.then(data => handleResult(g.next(data)), error => {
g.throw(error)
})
}
handleResult(g.next())
来封装一个统一的co模块
// 封装
function co(generator) {
const g = generator()
function handleResult(result) {
if (result.done) return // 生成器函数结束
result.value.then(data => handleResult(g.next(data)), error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
网友评论