杭州网站建设_数据库开发网站_大数据网站开发中国域名注册局官网
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 加载一张图片
// 加载函数 ...
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. 读代码
// 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 函数形式
// 异步
console.log(100)
setTimeout(function () {console.log(200)
}, 1000)
console.log(300)
// 输出结果 100、300、200
// 同步
console.log(100)
aleart(200)
console.log(300)
// 输出结果 100、200、300
(二)
异步的应用场景
- 网络请求, 如 ajax 图片加载
- 定时任务, 如 setTimeout
网络请求
// 网络请求 ajax
console.log('start')
$.get('./data1.json', function (data1) {console.log(data1)
})
console.log('end')
// 执行结果 start end data1
// 图片懒加载
console.log('start')
let img = document.createElement('img')
img.onload = function() {console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')
// 执行结果 start end loaded
定时任务
// setTimeout
console.log(100)
setTimeout(function() {console.log(200)
}, 1000)
console.log(300)
// 执行结果 100 300 200
// setInterval
console.log(100)
setInterval(function() {console.log(200)
}, 1000)
console.log(300)
// 执行结果 100 300 200 200 ...
(三)
callback hell (回调地狱)
// 获取第一份数据
$.get(url1, (data1) => {console.log(data1)// 获取第二份数据$.get(url2, (data2) => {console.log(data2)// 获取第三份数据$.get(url3, (data3) => {console.log(data3) // 还可能获取更多的数据})})
})
解决回调地狱的方案就是, Promise
// 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))
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 的状态一经改变,便不可以修改
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
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 中。理解 JavaScript 的 async/await
5.1 两个异步请求如何合并?
使用 Promise
//定义两个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 解决哪些问题?
回调地狱
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 请求, 如果只有一个出错的话, 那么整个就会抛出异常, 不会继续执行
// 模拟异步操作
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
如果其中一个错误, 让成功的也能输出出来
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
, 就能捕获正常和错误的返回
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
async function fetchData() {try {const result = await fetch('...')} catch (err) {console.log(err)}
}fetchData()
await
的catch
await
返回一个 Promise
对象,Promise
对象有 then、catch
,我们可以在 catch
中捕获错误
fetchData().then().catch(err => console.log('发生错误:', err))
6.1 async/await 解决了什么问题?
解决了 异步问题, 可以 异步转同步
// 使用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
执行