一、JS异步的原理与进阶

1,128 阅读7分钟

1.event loop

1. 请描述event loop(事件循环/事件轮询)的机制
1.JS是单线程运行的.(如何执行?)
    a. 从前到后一行一行的执行
    b. 一旦有一行执行报错,则阻断后面代码的执行
    c. 先把同步代码执行完,再执行异步
2.异步要基于回调来实现
3.event loop 就是异步回调的实现原理

微信图片_20230401125423.png

a. 同步代码,一行一行放在Call Satck执行
b. 遇到异步,会先"进行记录",等待时机(定时,网络请求)
c. 时机(时间到了),移动到callback queue(回调队列)中
d. 如果CallSatck为空(同步代码执行完) Event Loop 开始工作
e. 开始轮询查找Callback Queue,找到之后移动到Call Stack 执行
f. 然后继续轮询查找,直至为空
2. DOM事件和event loop事件循环
<button id='btn'>提交</button>
<script>
    console.log('hi');  
    $('#btn').click(() => {
        console.log('button clicked');
    })
    console.log('Bye');
    // 它会先执行一遍 hi---$('#btn').click()---Bye----用户点击的时候触发回调
</script>
1.JS是单线程的
2.异步(setTimeout, ajax等) 使用回调,基于event loop
3.DOM 事件也使用回调,基于event loop
    dom事件虽然是基于event loop实现,但它不是异步

Promise 进阶

1.Promise有哪三种状态? 是如何变化的?
1.三种状态
    pending(初始化) 
    fulfilled(成功) 
    rejected(失败)
2.状态的表现和变化
    变化是不可逆的, promise状态一旦被改变,就不允许被改变了
    -----pending---> resolved 或 pending ---> rejected
        const p1 = new Promise((res,rej) => {})
        console.log('p1', p1);  // pending

        const p2 = new Promise((res,rej) => {
            setTimeout(() => {
                res();
            })
        })
        console.log('p2', p2);  // 开始打印时候为pending
        setTimeout(() => console.log('p2-setTimeout', p2));  // res

        const p3 = new Promise((res,rej) => {
            setTimeout(() => {
                rej();
            })
        })
        console.log('p1', p3);  // pending
        setTimeout(() => console.log('p3-setTimeout', p3));  // rej
    表现
        pending   不会触发then和catch
        resolved  会触发后续的then回调函数
        reject    会触发后续的catch回调函数
        const w1 = Promise.resolve(10);  // resolved
        w1.then(res => console.log('res', res))  // 10
          .catch(err => console.log('err', err)) // 不会触发
          
        const w2 = Promise.resolve(10);  // rejected
        w2.then(res => console.log('res', res))  // 不会触发
          .catch(err => console.log('err', err)) // 10
    
3.then和catch对状态的影响
    then 正常返回resolved,里面有报错则返回rejected
        const w1 = Promise.resolve().then(() => 10)
        console.log('w1', w1); // resolved 触发 then回调
        w1.then(() => {
            console.log('123);  // 触发执行输出123
        });   

        const w2 = Promise.resolve().then(() => {
            throw new Error('then error')
        })
        w2.then(() => {
            console.log('456')  // 不会被触发执行
        }).catch(err => {
            console.error('error', err)  // then error
        })
        console.log('w2', w2); // rejected 触发 catch 回调
        
    catch 正常返回resolved,里面有报错则返回rejected
        const p4 = Promise.reject('then error').catch(err => console.log('err', err))
        console.log('p4', p4);  // resolved? 为什么? 因为只要正常返回那就是resolved  触发 then
        p4.then(() => {
            console.log(100);  // 100
        })
        const p5 = Promise.reject('then error').catch(err => {
            throw new Error('catch error')
        })
        console.log('p5', p5);  // rejected  触发 catch  
        p5.then(() => {
            console.log(200);    // 不会执行
        }).catch(() => {
            console.log('error');  //  error
        })  // resloved
2.Promise .then和.catch的连接
场景一
Promise.resolve().then(() => {   // Promise.resolve()  返回resolve的promise
    console.log(1);  // 1
}).catch(() => {  // 所以catch 不会被执行
    console.log(2);
}).then(() => {
    console.log(3);  // 3
}) // resolved
场景二
Promise.resolve().then(() => {  // 返回 rejected的promise
    console.log(1);  // 1
    throw new Error('error');
}).catch(() => {  // 没有报错 返回 resolved 的 promise
    console.log(2);  // 2
}).then(() => {
    console.log(3);  // 3
})  // 因为最后一个也没有报错会返回resolved
场景三
Promise.resolve().then(() => { // rejected 的promise  触发 catch
    console.log(1);            // 1
    throw new Error('error');
}).catch(() => {               // 返回 resolved 的 promise 后面是不会被执行的  且触发 then 
    console.log(2);            // 2
}).catch(() => {               // 不会再被执行
    console.log(3);
})
3. Promise和setTimeout的顺序

场景一

console.log(100)  // 1
setTimeout(() => {
    console.log(200);  // 4 宏任务
})
Promise.resolve().then(() => {
    console.log(300);  // 3  微任务
})
console.log(400);  // 2

async / await

1.场景题 async/await语法
(1)基本应用
1.最原始的办法 callback hell (回调地狱) 缺点 层层嵌套
2.Promise then catch 链式调用,也是基于回调函数
3.async / await 同步语法, 同步代码编写异步语法,彻底解决回调函数
const src1 = "地址";
const src2 = "地址";
function loadImg (src) {
    const p = new Promise((res,rej) => {
        const img = document.createElement('img')
        img.onload = () => {
            res(img) // resolved
        }
        img.onerror = () => {
            const err = new Error(`图片加载失败${src}`)
            rej(err) // rejected
        }
        img.src = src
    })
    return p
}
// !的意识是防止 在定义时候src1 src2 中间没有用分号分割 会把src1/2当成一个方法来执行就会报错
!(async function () {
    // 同步写法执行代码
    const img1 = await loadImg(src1)
    console.log(img1.height, img1.width);
    const img2 = await loadImg(src2)
    console.log(img2.height, img2.width);
})()

1.async/await 是避免使用异步回调的一种方法 不过现在也没人用异步回调解决
2.但和Promise并不相互排斥
3.反而俩者相辅相成
(2).async/await和Promise的关系
1.执行async函数,返回的是一个Promise对象
2.await 相当于 Promise 的 then
3.try...catch 可捕获异常,代替了Promisecatch 

// 示例
async function fn1 () {
    return 100;  //  === return Promise.resolve(100)
}
const res = fn1(); // 执行async函数,返回的是一个Promise对象 resolved
res.then(v => console.log('data', v))  // 200 

// await 返回 promise 对象  相当于 then 
!(async function () {
    const p1 = Promise.resolve(300)
    const res = await p1        // 1. await 相当于 Promise then
    const res1 = await 400      // 2. await Promise.resolve(400)
    const res2 = await fn1()    // 3. await Promise.resolve(100)
    console.log('res', res);    // 300
    console.log('res1', res1);  // 400
    console.log('res2', res2);  // 100
})()

// try catch 捕获
!(async function () {
    const p1 = Promise.reject('err1'); // rejected 状态
    try {
        const res = await p1;
        console.log(res, '---res--');
    } catch (error) {
        console.log(error);  // try...catch 相当于 promise catch
    }
})()

// 错误示例
!(async function () {
    const p1 = Promise.reject('error111')  // rejected 状态
    const res = await p1; // 因为await 相当于 then  但是它的状态是rejected  肯定不会执行到await
    console.log(res, '---res--');
})()
(3)场景题
// 示例1
(async function () {
    console.log('start');  // start
    const a = await 120
    console.log('a', a);   // 120
    const b = await Promise.resolve(120)
    console.log('b', b);   // 120
    const c = await Promise.reject(300)  // 报错, 因为await 相当于 promise .then
    console.log('c', c);
    console.log('end');
})()  // 执行完之后,打印哪些内容? start 120 120 

// 示例2
async function fn() {
    return 100
}
(async function () {
    const a = fn() // promise对象
    const b = await fn() // 100
})()
2. async / await 的顺序问题
(1)场景题
async function async1 () {
    console.log('async1 start');  // 2
    await async2()  
    // await 后面的会做为 异步微任务
    console.log('async1 end');   // 6
}
async function async2 () {
    console.log('async2');     // 3
}
console.log('script start');   // 1
setTimeout(() => {
    console.log('setTimeout');  // 8
}, 0)
async1()

// new 初始化 promise 时, 传入的函数会立刻被执行
new Promise (function(resolve) {
    console.log('promise1');  // 4
    resolve() // 执行resolve之后 初始化的整个状态就是resloved
}).then(function(){    // 微任务
    console.log('promise2');  // 7
})
console.log('start end'); // 5 // 此段代码 依次打印输出什么?

微任务 / 宏任务

1.什么是宏任务(macroTask)和微任务(microTask)、两者有什么区别?

1.宏任务: DOM 渲染后触发 例: setTimeout
    setTimeout, setInterval, Ajax, DOM 事件

2.微任务: DOM 渲染前触发,例: Promise (执行时机比宏任务早) 
    Promise async/await 
    
3.event loop 和 DOM 渲染
    每次 Call Satck 清空(即每次轮询结束), 同步任务执行完
    都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
    然后再去触发下一次Event loop
4. 为什么先执行微任务后执行宏任务?
    1. 同步代码,一行一行放在Call Satck执行
    2. 遇到异步,会先"进行记录",等待时机(定时,网络请求)
    3. 时机到了,移动到callback queue(回调队列)中
    4. 如果CallSatck为空(同步代码执行完) Event Loop 开始工作
    5. callback queue 回调队列中还会分为微任务/宏任务 ,先执行微任务,然后进行尝 试DOM渲染 
    6. 触发event loop 轮询查找Callback Queue,找到之后移动到Call Stack 执行
    7. 然后继续轮询查找,直至为空
5.微任务和宏任务区别?
    1. 微任务是es6 语法规定的
    2. 宏任务是由浏览器规定的
    3. 微任务执行时机比宏任务早
    4. 宏任务: DOM 渲染后触发
    5. 微任务: DOM 渲染前触发

手写Promise-构造函数

1.初始化 & 异步调用 
2.then catch 链式调用
3.API .resolve .reject .all .race

(1)构造函数实现

/**
 * @description MyPromise
 * @author Amorous
 */
class MyPromise {
    state = 'pending'  // 状态, 'pending' 'fulfilled' 'rejected'
    value = undefined  // 成功后的值
    reason = undefined // 失败后的原因
    resolveCallbacks = []  // pending 状态下 存储成功的回调
    rejectedCallbacks = [] // pending 状态下 存储失败的回调
    constructor(fn) {
        const resolveHandler = (value) => {
            if(this.state === 'pending') {
                this.state === 'fulfilled'
                this.value === value
                this.resolveCallbacks.forEach(fn => fn(this.value))
            }
        }
        const rejectedHandler = (reason) => {
            if(this.state === 'pending') {
                this.state === 'rejected'
                this.reason === reason
                this.resolveCallbacks.forEach(fn => fn(this.reason))
            }
        }
        try {
            fn(resolveHandler, rejectedHandler)
        } catch (err) {
            rejectedHandler(err)
        }
    }
    then()
    catch()
    resolve()
    reject()
    all()
    race()
 }

(2).then 实现

   then(fn1, fn2) {
       fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
       fn2 = typeof fn2 === 'function' ? fn2 : (err) => err
       const { state } = this;
       if (state === 'pending') {
           const p1 = new MyPromise((resolve, reject) => {
               this.resolveCallbacks.push(() => {
                   try {
                       const newValue = fn1(this.value)
                       resolve(newValue)
                   } catch (error) {
                       reject(error)
                   }
               })
               this.rejectedCallbacks.push(() => {
                   try {
                       const newReason = fn2(this.reason)
                       reject(newReason)
                   } catch (error) {
                       reject(error)
                       
                   }
               })
           })
           return p1
       }
       if (state === 'fulfilled') {
           const p1 = new MyPromise((resolve, reject) => {
               try {
                   const newData = fn1(this.value);
                   resolve(newData)
               } catch (error) {
                   reject(err)
               }
           })
           return p1
       }
       if (state === 'rejected') {
           const p1 = new MyPromise((resolve, reject) => {
               try {
                   const newReason = fn2(this.reason);
                   reject(newReason)
               } catch (error) {
                   reject(err)
               }
           })
           return p1
       }
   }
}

(3).catch 实现

// 就是then的一个语法糖,简单模式
catch (fn) {
    return this.then(null, fn)
}

(4)resolve 实现

MyPromise.resolve = function(value) {
    return new MyPromise((resolve, reject) => resolve(value))
}

(5)reject 实现

// reject
MyPromise.reject = function(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
}

(6)all 实现

MyPromise.all = function (promiseList = []) {
    const p1 = new MyPromise((resolve, reject) => {
        const result = [] // 存储 promiseList 所有的结果
        const length = promiseList.length
        let resolvedCount = 0;
        promiseList.forEach(p => {
            p.then(data => {
                result.push(data)
                // resolvedCount 必须在then里面做++ .then执行了才++
                resolvedCount++
                if (resolvedCount === length) {
                    // 已经遍历到了最后一个 promise
                    resolve(result)
                }
            }).catch(err => {
                reject(err)
            })
        })
    })
    return p1
}

(7)race 实现

MyPromise.race = function (promiseList = []) {
    let resolved = false  // 标记
    const p1 = new Promise((resolve, reject) => {
        promiseList.forEach(p => {
            p.then(data => {
                if (!resolved) {
                    resolve(data)
                    resolved = true
                }
            }).catch(err => {
                reject(err)
            })
        })
    })
    return p1
}