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
实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve
和 reject
方法,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/await
。 async/await
函数对 Generator
函数的改进
体现在以下三点: - 内置执行器。 Generator
函数的执行必须靠执行器,而 async
函数自带执行器。
也就是说,async
函数的执行,与普通函数一模一样,只要一行。 更广的适用性。
yield
命令后面只能是 Thunk
函数或 Promise
对象,而 async
函数的 await
命令后面,可以跟 Promise
对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作), 更好的语义。
async
和 await
,比起 星号 和 yield
,语义更清楚了
async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是 promise
和 async/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 如果接收错误
- catch
- 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 值,如基本数据类型(number
、string
、boolean
等)、对象、数组等。在这种情况下,await
会将该值直接返回,就好像该值被包装在一个已经解决的 Promise 中。
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有哪几种状态,各个状态之间是如何进行转换的?
三种状态: pending
、fulfilled
、rejected
(未决定,履行,拒绝)
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 函数嘛
-
那它可以添加一个普通函数吗,能正确执行吗?
可以添加一个普通函数
-
那可以添加一个值吗?
可以的,直接返回那个值
为什么 await 后面可以普通函数,或者值?
因为await
后面跟的是一个 Promise
对象,如果不是,则会包裹一层 Promise.resolve()

语法规则
async
是function
的一个前缀,只有async
函数中才能使用await
语法async
函数是一个Promise
对象,有无resolve
取决于有无在函数中return
值await
后面跟的是一个Promise
对象,如果不是,则会包裹一层Promise.resolve()
async await 原理
async/await
是由 generator函数(迭代器)
来实现的
async await 如何捕获异常
try catch
javascript
async function fetchData() {
try {
const result = await fetch('...')
} catch (err) {
console.log(err)
}
}
fetchData()
await
的catch
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中的实现通常利用Promise
的then
方法来创建一个微任务
-
Vue
中watch
与computer
哪个是同步、哪个是异步?computer
是同步,watch
是异步 -
watch
与computer
哪个先执行?需要根据实际情况决定,正常情况下是
computer
先执行如果
watch
设置了immediate: true
,watch
先于computer
执行