javaScript-系统知识点【同步 和 异步】

1. 同步和异步的区别?

基于 JS 是单线程语言

  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

1.1 什么是同步、异步?

同步: 按代码顺序执行

异步: 简单来说, 不按照代码顺序执行, 就是异步

1.2 为什么会有异步?

异步是为了解决, JS 单线程阻塞问题的

1.3 如何 异步 解决 JS 单线程阻塞问题?

通过 事件循环 来解决, 事件循环的执行流程, 同步任务会进入主线程执行, 而异步任务会进入任务队列, 等到主线程任务执行完, 任务队列的任务就会放入主线程执行, 如此循环反复

1.4 JS 如何实现异步?

异步在于创建宏任务和微任务, 通过事件循环机制实现异步机制

宏任务

  • 定时器 setTimeout、setInterval
  • 事件监听 (发布订阅 postMessage)
  • 回调函数
  • I/O

微任务

  • Promise
  • async/await

标准回答 (按异步编程进化史来说)

所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。

回调函数是异步操作最基本的方法,比如 AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误

return Promise 包装了一个异步调用并生成一个 Promise 实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolvereject方法,then接收到对应的数据,做出相应的处理。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消 Promise,错误需要通过回调函数捕获。

Generator(迭代器) 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代Generator函数很麻烦,实现逻辑有点绕

async/await是基于Promise实现的,async/await使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

加分回答 JS 异步编程进化史:callback -> promise -> generator/yield -> async/awaitasync/await 函数对 Generator 函数的改进

体现在以下三点: - 内置执行器。 Generator 函数的执行必须靠执行器,而 async 函数自带执行器。

也就是说,async 函数的执行,与普通函数一模一样,只要一行。 更广的适用性。

yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作), 更好的语义。

asyncawait,比起 星号 和 yield,语义更清楚了

async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是 promiseasync/await

2. 手写用 Promise 加载一张图片

javascript 复制代码
// 加载函数 ...
function loading (src) {
    return new Promise(
    	(resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
}

const url1 = 'https://img.com/img1.jpg'
const url2 = 'https://img.com/img2.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 对象
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

3. 前端使用异步的场景有哪些?

场景

  • 网络请求
  • 定时任务

4. 读代码

javascript 复制代码
// setTimeout 笔试题
console.log(1)
setTimeout(function() {
  	console.log(2)         
}, 1000)
console.log(3)
setTimeout(function() {
    console.log(4)
}, 0)
console.log(5)
// 执行结果 1 3 5 4 2

下面就是相关知识点


知识点:

  • 单线程和异步

  • 应用场景

  • callback hell (回调地狱) 和 Promise

(一)

单线程和异步

  • JS 是单线程语言, 只能同时做一件事儿
  • 浏览器和 nodejs 已支持 JS 启动进程, 如 Web Worker
  • JS 和 DOM 渲染共同一个线程, 因为 JS 可修改 DOM 结构
  • 遇到等待 (网络请求, 定时任务) 不能卡住, 所以需要异步, 以回调 callback 函数形式
javascript 复制代码
// 异步
console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300)
// 输出结果 100、300、200
javascript 复制代码
// 同步
console.log(100)
aleart(200)
console.log(300)
// 输出结果 100、200、300

(二)

异步的应用场景

  • 网络请求, 如 ajax 图片加载
  • 定时任务, 如 setTimeout

网络请求

javascript 复制代码
// 网络请求 ajax
console.log('start')
$.get('./data1.json', function (data1) {
    console.log(data1)
})
console.log('end')
// 执行结果 start end data1
javascript 复制代码
// 图片懒加载
console.log('start')
let img = document.createElement('img')
img.onload = function() {
 	console.log('loaded')   
}
img.src = '/xxx.png'
console.log('end')
// 执行结果 start end loaded

定时任务

javascript 复制代码
// setTimeout
console.log(100)
setTimeout(function() {
    console.log(200)
}, 1000)
console.log(300)
// 执行结果 100 300 200
javascript 复制代码
// setInterval
console.log(100)
setInterval(function() {
    console.log(200)
}, 1000)
console.log(300)
// 执行结果 100 300 200 200 ...

(三)

callback hell (回调地狱)

javascript 复制代码
// 获取第一份数据
$.get(url1, (data1) => {
    console.log(data1)
    // 获取第二份数据
    $.get(url2, (data2) => {
        console.log(data2)
        // 获取第三份数据
        $.get(url3, (data3) => {
            console.log(data3) 
            // 还可能获取更多的数据
        })
    })
})

解决回调地狱的方案就是, Promise

javascript 复制代码
// Promise 定义
function getData(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            success(data) {
                resolve(data)
            },
            error(err) {
                reject(err)
            }
        })
    })
}
// Promise 使用
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'

getData(url1).then( data1 => {
    console.log(data1)
    return getData(url2)
}).then( data2 => {
    console.log(data2)
    return getData(url3)
}).then(data3 => {
    console.log(data3)
}).catch(err => console.error(err))
javascript 复制代码
let approvalProcess = (name. time) => {
  return new Promise((resolve, reject) => { 
    setTimeout(() => { // setTimeout 模拟异步
      let approvalRes = Math.random() >= 0.2 // 随机数模拟异步成功操作成功或失败
      if (approvalRes) {
        resolve(name+'已审核通过')
      } else {
        reject('审核不通过')
      }
    }, time)
  })
}

let boss = approvalProcess('老板', 1000)
let manager = approvalProcess('经理', 2000)

boss.then(res => {
	console.log(res)
  return manager
}).then(res => {
	console.log(res)
  return '老板和经理都通过了'
}).then(result => {
	console.log(result)
}).catch(err => {
	console.log(err)
})

[扩展] - promise 经典面试题

5. Promise

Promise 的状态?

Promise 有三种状态:pengding、fulfilled、rejected

Promise 的状态一经改变,便不可以修改

javascript 复制代码
var pro = new Promise( (resolve, reject) => {
    reject()
    resolve()
})

pro.then( () => { console.log('resolve1') }).
	catch( () => {console.log('catch1') }) // reject1

Promise 链式调用

Promise 的链式调用,有三个 Promise.prototype.then()Promise.prototype.catch()Promise.prototype.finally()

Promise.prototype.then() then 方法可以接收两个回调函数作为参数,第一个参数 resolve() 返回的数据,第二个参数 reject() 返回的数据

当然了,异常也会被第二个参数接收

.finally() 一定会执行,但是它没有回调参数

.then() 可有多个,.catch() 也可以有多个,但是 .then() 或者 .catch() 必须返回一个 Promise 才可以这样做

数据的接收顺序

  • then -> catch -> finally
javascript 复制代码
var pro = new Promise( (resolve, reject) => {
    reject()
    resolve()
})

pro.then(
    () => { console.log('resolve1') },
    () => { console.log('reject1') }).
	catch( () => {console.log('catch1') }
) // reject1

只有 .then() 的第二个参数传,reject() 返回的数据 或者是 异常才会进到 .catch()

[注意] Promise 抛出异常是不会,直接中断的,会进入 .then() 的第二个参数,没有 .then() 的第二个参数才会 进入 .catch()

Promise 如果接收错误

  1. catch
  2. then 的第二个回调函数参数

Promise 的一些方法

  • Promise.resolve() 返回 成功状态

  • Promise.reject() 返回 失败状态

  • Promise.finally() 不管什么状态都执行

  • Promise.then() 成功回调

  • Promise.catch() 错误回调

  • Promise.all() 一个 reject(), 整个结束执行 (获取全部都成功,再返回)

  • Promise.allSettled() 全部状态变更,才执行结束

  • Promise.any() 一个 resolve(),整个再返回 (获取全部都失败,再返回)

  • Promise.race() 那个状态先改变,那个先返回

await 后面可以跟 Promise 对象、非 Promise 值以及另一个 await 表达式。

await 后面也可以跟非 Promise 值,如基本数据类型(numberstringboolean 等)、对象、数组等。在这种情况下,await 会将该值直接返回,就好像该值被包装在一个已经解决的 Promise 中。

理解 JavaScript 的 async/await

5.1 两个异步请求如何合并?

使用 Promise

typescript 复制代码
//定义两个http请求方法
const getList1 = ()=>{
    return new Promise((res,rej) =>{
        //省去get方法获取过程
        .then((json) => resolve(json))
    })
}

const getList2 = ()=>{
    return new Promise((res,rej) =>{
        //省去get方法获取过程
        .then((json) => resolve(json))
    })
}

Promise.all([getList1(),getList2()]).then(value => {
    //第一个请求的数据
    const x = value[0];

    //第二个请求的数据
    const y = value[1];

    //合并操作
    for(const i of x){
        for(const k of y){
          //Todo
        }
    }
})

5.2 Promise有哪几种状态,各个状态之间是如何进行转换的?

三种状态: pendingfulfilledrejected (未决定,履行,拒绝)

1.初始化,状态:pending

2.当调用resolve(成功),状态:pengding=>fulfilled

3.当调用reject(失败),状态:pending=>rejected

5.3 Promise 解决哪些问题?

回调地狱

typescript 复制代码
const request = url => {
    return new Promise((resolve,reject) => {
        $.get(url,params => {
            resolve(params)
        })
    })
}

request(url).then(params1 => {
    return request(params1.url)
}).then(params2 => {
    return request(params2.url)
}).then(params3 => {
    console.log(params3)
}).catch(err => throw new Error(err))

5.4 Promise.all、Promise.any、Promise.race、Promise.allsettled

Promise.all

场景: 多个 Promise 请求, 如果只有一个出错的话, 那么整个就会抛出异常, 不会继续执行

javascript 复制代码
// 模拟异步操作
const request = (delay, flag = true) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (flag) {
        resolve(`成功了${delay}`)
      } else {
        reject(`失败了${delay}`)
      }
    }, delay)
  })
}

const fun = async (promises) => {
  Promise.all(promises)
    .then(res => {
      console.log('res', res)
    })
    .catch(error => {
      console.log('error', error)
    })
}

fun([request(1000), request(500)])
// res [ '成功了1000', '成功了500' ]
fun([request(1000), request(500, false)])
// error 失败了500

如果其中一个错误, 让成功的也能输出出来

javascript 复制代码
const fun = async (promises) => {
  Promise.all(
    promises.map(promise => {
      console.log(promise.catch(err => err))
      return promise.catch(err => err)
    })
  ).then(res => {
    console.log('res', res)
  })
}

fun([request(1000), request(500, false)])
// res [ '成功了1000', '失败了500' ]

使用 ES2020 (ES11) 的新语法 Promise.allSettled, 就能捕获正常和错误的返回

javascript 复制代码
const fun = async (promises) => {
  Promise.allSettled(promises)
    .then(res => {
      console.log('res', res)
    })
}

fun([request(1000), request(500, false)])
// res [
//   { status: 'fulfilled', value: '成功了1000' },
//   { status: 'rejected', reason: '失败了500' }
// ]

6. async await

await 通常是添加一个 promise 函数嘛

  1. 那它可以添加一个普通函数吗,能正确执行吗?

    可以添加一个普通函数

  2. 那可以添加一个值吗?

    可以的,直接返回那个值

为什么 await 后面可以普通函数,或者值?

因为await 后面跟的是一个 Promise 对象,如果不是,则会包裹一层 Promise.resolve()

语法规则

  • asyncfunction 的一个前缀,只有 async 函数中才能使用 await 语法
  • async 函数是一个 Promise 对象,有无 resolve 取决于有无在函数中 return
  • await 后面跟的是一个 Promise 对象,如果不是,则会包裹一层 Promise.resolve()

async await 原理

async/await 是由 generator函数(迭代器) 来实现的

async await 如何捕获异常

  1. try catch
javascript 复制代码
async function fetchData() {
    try {
        const result = await fetch('...')
    } catch (err) {
        console.log(err)
    }
}

fetchData()
  1. awaitcatch

await 返回一个 Promise 对象,Promise 对象有 then、catch,我们可以在 catch 中捕获错误

javascript 复制代码
fetchData().then().catch(err => console.log('发生错误:', err))

6.1 async/await 解决了什么问题?

解决了 异步问题, 可以 异步转同步

typescript 复制代码
// 使用async/await获取成功的结果
// 定义一个异步函数,3秒后才能获取到值(类似操作数据库)

function getSomeThing(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('获取成功')
        },3000)
    })
}

async function test(){
    let a = await getSomeThing()
    console.log(a)
}
test() // 3秒后输出:获取成功

7. 事件循环

什么是事件循环?

js 是单线程, 而为了解决这个问题, 引入了异步, 同步任务会进入主线程执行, 而异步任务会进入任务队列, 等到主线程任务执行完, 任务队列的任务就会放入主线程执行, 如此循环反复就是事件循环。

事件循环中的异步任务?有哪些能举例吗?

异步任务可以分为微任务、宏任务

宏任务:定时器、请求、事件监听 (发布订阅 postMessage)、I/O

微任务:promise、async/await

宏任务与微任务那个先执行?

在同一次循环中,微任务先执行,宏任务后执行

网上一些面试题,有些执行结果是 宏任务先给出结果,但其内部的微任务仍然会在该宏任务完成之前被优先执行

Vue的$nextTick方法是微任务还是宏任务?

$nextTick在Vue中的实现通常利用 Promisethen方法来创建一个微任务

  1. Vuewatchcomputer 哪个是同步、哪个是异步?

    computer 是同步,watch 是异步

  2. watchcomputer 哪个先执行?

    需要根据实际情况决定,正常情况下是 computer 先执行

    如果 watch 设置了 immediate: truewatch 先于 computer 执行

相关推荐
土豆炒马铃薯。3 分钟前
【Java 基础(人话版)】Java 虚拟机(JVM)
java·开发语言·jvm·后端·java基础·虚拟机
程序员徐师兄8 分钟前
基于Python Django的人脸识别上课考勤系统(附源码,部署)
开发语言·python·django·人脸识别考勤·人脸识别上课考勤系统
Pandaconda11 分钟前
【新人系列】Golang 入门(二):基本数据类型
开发语言·笔记·后端·golang·go·字符串·数据类型
轩源源12 分钟前
数据结构——哈希表的实现
开发语言·数据结构·c++·算法·哈希算法·散列表·哈希桶
编码浪子21 分钟前
基于 Rust 与 GBT32960 规范的编解码层
java·开发语言·rust
lsx20240631 分钟前
C# 类型转换
开发语言
学习使我变快乐33 分钟前
C++:内联函数
开发语言·c++
阿拉希神猪34 分钟前
基于log4j的自定义traceid实现
java·开发语言·log4j
X_StarX43 分钟前
计算机基础面试(数据结构)
数据结构·面试·职场和发展
Wangawf1 小时前
python小游戏-坦克大战
开发语言·python·pygame