事件循环
js是一个单线程语言,意味着所有的任务都只能顺序执行,如果所有的任务都是同步进行的,那么如果进行一个请求,就必须要等待请求结果返回,才能去做其他的事情,这如果是一个很慢的请求,那么对于交互而言,无疑是灾难性的,所以异步任务尤为重要,注册一个回调事件,当异步任务完成之后,再去执行回调函数。
宏任务 微任务
举个例子,大爷去银行排队办理业务,那么一个大爷就是一个宏任务,大爷办的一个业务就是一个微任务,一个大爷可能会办好几个业务
哪些是宏任务
- IO
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
- UI rendering
哪些是微任务
- Promise.then
- async await
- process.nextTick
- MutationObserver
事件循环的进程模型
- 选择当前要执行的任务队列,选择任务队列中最先进入的任务,如果任务队列为空即
null,则执行跳转到微任务(MicroTask)的执行步骤。 - 将事件循环中的任务设置为已选择任务。
- 执行任务。
- 将事件循环中当前运行任务设置为null。
- 将已经运行完成的任务从任务队列中删除。
- microtasks步骤:进入microtask检查点。
- 更新界面渲染。
- 返回第一步。
实现一个事件的发布订阅
class eventEmit {
constructor(){
this.events = {}
}
on(event, fn) {
if(!this.events[event]){
this.events[event] = [fn];
} else {
this.events[event].push(fn)
}
}
off(event, fn) {
if(!this.events[event] || !this.events[event][fn]) {
throw new Error('无此事件');
return;
}
this.events[event].filter(item => item !== fn)
}
once(event, fn) {
fn.once = true;
this.on(event, fn);
}
emit(event, data) {
this.events[event].forEach(cb => {
cb(data);
if(cb.once){
this.off(event, cb);
}
})
}
}
Promise
promise是es6提出的概念,本质是一个状态机,有三种状态,分别是pedding、fullfilled、rejected,状态的改变是单向的,只能从pedding变成其他两种状态,内置then方法,then方法返回一个promise,所以then方法可以被链式调用。
实现一个Promise
function myPromise(exector) {
let self = this;
self.status = "pedding";
self.data = undefined;
self.onResolvedCallback = [];
self.onRejectCallback = [];
function resolve(value) {
if (self.status === "pedding") {
self.status = "fullfilled";
self.data = value;
for (let i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
}
function reject(reason) {
if (self.status === "pedding") {
self.status = "rejected";
self.data = reason;
for (let i = 0; i < self.onRejectCallback.length; i++) {
self.onRejectCallback[i](reason);
}
}
}
try {
exector(resolve, reject);
} catch (e) {
reject(e);
}
}
实现then方法 考虑链式调用
myPromise.prototype.then = (onResolved, onRejected) => {
let self = this;
let promise2;
onResolved = typeof onResolved === "function" ? onResolved : () => {};
onRejected = typeof onRejected === "function" ? onRejected : () => {};
if (self.status === "fullfilled") {
return (promise2 = new myPromise(function (resolve, reject) {
try {
let x = onResolved(self.data);
if (x instanceof myPromise) {
x.then(resolve, reject);
}
resolve(x);
} catch (e) {
reject(e);
}
}));
}
if (self.status === "rejected") {
return (promise2 = new myPromise(function (resolve, reject) {
try {
let x = onRejected(self.data);
if (x instanceof myPromise) {
x.then(resolve, reject);
}
} catch (e) {
reject(e);
}
}));
}
if (self.status === "pedding") {
return (promise2 = new myPromise(function (resolve, reject) {
self.onResolvedCallback.push(function () {
try {
let x = onResolved(self.data);
if (x instanceof Promise) {
x.then(resolve, reject);
}
} catch (e) {
reject(e);
}
});
}));
}
};
实现一个promise.all
function promiseAll(promises) {
if(!Array.isArray(promises)) {
throw new Error('传参错误')
}
let n = 0;
let result = [];
promises.forEach((item, index) => {
Promise.resolve().then(res => {
result[index] = res;
n++;
if(n === promises.length){
Promise.resolve(result)
}
}).catch(e => {
Promise.reject(e);
})
})
}
实现一个promise.race
function promiseRace(promises){
return new Promise(resolve, reject) {
for(let i in promises) {
Promise.resolve(i).then(res => {
resolve(res);
}, e => reject(e))
}
}
}
async await的产生背景
因为promise虽然解决了回调地狱的问题,但是写法来看,还是存在的链式调用不雅观的问题,特别是多个then链式调用的时候, 所以await是用来利用同步的方式去调用异步的代码。
request(5).then(res1 => {
console.log(res1) // 1秒后 输出 10
request(res1).then(res2 => {
console.log(res2) // 2秒后 输出 20
})
})
async function fn () {
const res1 = await request(5)
const res2 = await request(res1)
console.log(res2) // 2秒后输出 20
}
fn()
async的要点
- await只能在含有async的函数里使用
- async函数返回一个Promise,是否有值,取决于是否return
- await最好是接一个promise,虽然用其他的也可以达到排队的效果
- async/await的作用是用同步的方式去执行异步的代码
- async函数其实是语法糖,本质是generator函数
generator函数
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
generator函数跟普通函数相比,就是在函数名前面多一个*,只有在generator函数内,才能使用yield,yield相当于是暂停器,可以使用next方法去继续执行,next方法会返回上一步yield的返回值跟是否执行完成的状态标志done。
yield接函数
function fn(num) {
console.log(num)
return num
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
// 1
// { value: 1, done: false }
console.log(g.next())
// 2
// { value: 2, done: false }
console.log(g.next())
// { value: 3, done: true }
yield接Promise
function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }
如果我们想要promise的结果,那么应该怎么处理呢?
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
console.log(res1) // 1秒后输出 1
const next2 = g.next()
next2.value.then(res2 => {
console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
console.log(res2) // 2秒后输出 2
console.log(g.next()) // 2秒后输出 { value: 3, done: true }
})
})
如果想要传递参数
function fn(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传入上次的res1
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传入上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})
实现一个async/await
function generatorToAsync(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments) // gen有可能传参
// 返回一个Promise
return new Promise((resolve, reject) => {
function go(key, arg) {
let res
try {
res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
} catch (error) {
return reject(error) // 报错的话会走catch,直接reject
}
// 解构获得value和done
const { value, done } = res
if (done) {
// 如果done为true,说明走完了,进行resolve(value)
return resolve(value)
} else {
// 如果done为false,说明没走完,还得继续走
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
}
}
go("next") // 第一次执行
})
}
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))