在js语言设计之初,主要是用来操作dom。假设js是多线程的,那么在一个线程里对dom进行修改操作,另一个线程进行删除操作,这样就会造成冲突。所以js被设计成了单线程。
由于js单线程的原因,如果在代码执行过程中碰到了耗时长的任务页面就会卡住。为了避免这种情况的产生,js采用了事件循环+消息队列来异步执行代码从而解决像网络IO这样耗时长的任务。
虽然js是单线程的但是浏览器并不是只有一个线程
js会在对应的时刻交给对应线程去执行
事件循环和消息队列
const fetch = require('node-fetch')
console.log('fetch before...')
fetch('https://www.baidu.com/')
.then(res => res.text())
.then(res => console.log(res))
console.log('fetch after...')
我们用上面的代码来模拟一次js中的异步工作:
- 主线程依次往下执行遇到
console.log('fetch before')直接打印 - 主线程遇到
fetch,由于fetch是个异步操作。为了不再这里继续等待,js主线程会将fetch操作交给网络线程。 - 主线程依次往下执行遇到
console.log('fetch after...')直接打印 - 主线程执行完后会去不断监听消息队列里面有没有任务可以执行。当异步请求线程获取到需要的数据后,会把任务推到消息队列里面。主线程监听到消息队列有任务后就取出来执行。
- 主线程不断的监听消息队列并且执行的过程叫做事件循环
消息队列工作方式
事件循环中存在着两种任务:
宏任务(Macro Task):setTimeout、setInterval、I/O、UI 交互事件(onclick之类的)
微任务(Micro Task):Promise、process.nextTick(Node.js 环境)
当主线程执行完会首先去取事件循环中的微任务来执行,之后才会去取宏任务执行
异步解决方案
Promise
可以把promise当作一个承诺,代码执行过程为pending成功返回fulfilled,失败返回rejected。
const dosome = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('fulfilled')
}
reject('rejected')
}, 1000)
})
dosome
.then((res) => {
console.log(res)
return 'fulfilled2'
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
// 如果promise成功则打印
// fulfilled
// fulfilled2
// 如果promise成功则打印
// rejected
promise中的静态方法
promise.resolve(): 返回一个成功后的promise对象prmise.all([task1, task2...]): 当所有任务都成功返回一个成功结果的数组promise.race([task1, task2...]): 当数组中的第一个任务成功返回第一个任务执行成功后的数据
Generator
generator(生成器)是ES6标准引入的新的数据类型。看起来像一个可以返回多次的函数。
function * foo () {
// 第一次带哦用next才执行
console.log('start')
const res = yield 'foo'
console.log(res)
}
// 并没有立即执行foo而是得到一个生成器对象
const generator = foo()
// 在只想到yield停止并返回
const result = generator.next()
console.log(result) // { value: 'foo', done: false }
// 接着yield之后执行并将传入参数给res
const result2 = generator.next('bar')
使用generator处理异步任务
const fetch = require('node-fetch')
function * foo () {
const url = yield fetch('https://www.baidu.com/')
yield fetch(url)
}
const g = foo()
const result = g.next()
result.value
.then(res => res.text())
.then(res => console.log(res))
const result2 = g.next('https://www.baidu.com/')
result2.value
.then(res => res.text())
.then(res => console.log(res))
Async和Await
现在最流行的写法
const fetch = require('node-fetch')
// 用async修饰的函数会变成promise对象
;(async function() {
// await必须用在async函数里面
const res = await fetch('https://www.baidu.com/').then(res=>res.text())
console.log(res)
})()
Promise源码
自己实现了一个简单的promise
const STATUS = {
PENDING: 'PENDING',
REJECTED: 'REJECTED',
FULFILLED: 'FULFILLED'
}
class MyPromise {
status = STATUS.PENDING
reason = undefined
value = undefined
rejectedQuene = []
fullfilledQuene = []
// new Promise传入的函数进来后会直接进行
constructor(callback) {
if (callback instanceof Function) {
try {
callback && callback(this.resolve.bind(this), this.reject.bind(this))
} catch(err) {
this.reject(err)
}
}
}
// 用于处理链式调用中then的返回值
// 如果then返回是普通值或者是prmise值的操作
// 这里不应该以static暴露到外面,这里这样写只是为了方便
static handlePromise(p1, value, resolve, reject) {
// then返回的promise和当前返回的promise一样会照成循环引用
if (p1 === value) {
throw 'can not do this: return promise';
}
if (value instanceof MyPromise) {
value.then(v => resolve(v), r => reject(r))
} else {
resolve(value)
}
}
resolve(value) {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
// 如果对应的事件队列有操作则取出消费
while(this.fullfilledQuene.length !== 0) {
this.fullfilledQuene.shift()(this.value)
}
}
}
reject(reason) {
// status值只有在Pending的时候才会进行转换
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = reason
// 如果对应的事件队列有操作则取出消费
while(this.rejectedQuene.length !== 0) {
this.rejectedQuene.shift()(this.reason)
}
}
}
then(successCallback, rejectCallback) {
const promise = new MyPromise((resolve, reject) => {
if (this.status === STATUS.FULFILLED) {
setTimeout(() => {
try {
const value = successCallback(this.value)
MyPromise.handlePromise(promise, value, resolve, reject)
} catch(err) {
reject(err)
}
}, 0)
} else if (this.status === STATUS.REJECTED) {
setTimeout(() => {
try {
const value = rejectCallback(this.reason)
MyPromise.handlePromise(promise, value, resolve, reject)
} catch(err) {
reject(err)
}
}, 0)
} else {
// 如果是异步操作就传入对应的事件队列
this.fullfilledQuene.push(() => {
setTimeout(() => {
try {
const value = successCallback(this.value)
MyPromise.handlePromise(promise, value, resolve, reject)
} catch(err) {
reject(err)
}
}, 0)
})
this.rejectedQuene.push(() => {
setTimeout(() => {
try {
const value = rejectCallback(this.reason)
MyPromise.handlePromise(promise, value, resolve, reject)
} catch(err) {
reject(err)
}
}, 0)
})
}
})
// 返回promise才可以进行链式调用
return promise
}
}
今日总结
- 事件循环和消息队列的概念,更多浏览器内部执行逻辑可以参阅书籍:
webkit技术内幕 - 基于异步而产生异步代码的解决方案
- promise源码