async 和 await
async 和 await 的基本使用
1、async用于修饰一个函数,表示一个函数异步的
如果async函数内没有await,那么async没有意义,全是同步内容
只有遇到了await开始往下,才是异步的开始
2、await 要用在 async 函数中
3、await 后面一般会跟一个promise对象,await会阻塞async函数的执行,
直到等到promise成功的结果(resolve的结果)
4、await 只会等待 promise 成功的结果,如果失败了会报错,需要 try catch
事件循环队列-宏任务微任务
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,Event Loop 的方案应用而生。Event Loop 包含两类:一类是基于 Browsing Context,一种是基于 Worker。
二者的运行是独立的,也就是说,每一个 JavaScript 运行的"线程环境"都有一个独立的 Event Loop,每一个 Web Worker 也有一个独立的 Event Loop。
宏任务:
setTimeout setInterval Ajax DOM事件 script(整体代码) I/OUI交互事件 postMessageMessage Channel setImmediate(Node.js 环境)
微任务:
Promise async/await Object.observe MutationObserver process.nextTick(Node.js 环境)
微任务比宏任务的执行时间要早
异步和单线程
异步和单线程是相辅相成的,js是一门单线程脚本语言,所以需要异步来辅助
异步和同步的区别:
异步不会阻塞程序的执行,
同步会阻塞程序的执行,
前端使用异步的场景:
定时任务:
setTimeout,setInverval
网络请求:
ajax请求,动态<img>加载
事件绑定
任务队列和event loop(事件循环)
1)所有的同步任务都在主线程上执行,行成一个执行栈。
2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。
3)主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。
4)重复上面三步。
只要主线程空了,就会读取任务列队,这就是js的运行机制,也被称为 event loop(事件循环)。
考察的是事件循环和回调队列。注意以下几点:
Promise 优先于 setTimeout 宏任务,所以 setTimeout 回调会最后执行
Promise 一旦被定义就会立即执行
Promise 的 resolve 和 reject 是异步执行的回调。所以 resolve() 会被放到回调队列中,在主函数执行完和 setTimeout 之前调用
await 执行完后,会让出线程。async 标记的函数会返回一个 Promise 对象
解析:
首先,事件循环从宏任务(macrostack)队列开始,这个时候,宏任务队列中,只有一个 script (整体代码)任务。从宏任务队列中取出一个任务来执行。
首先执行 console.log('script start'),输出 ‘script start'
遇到 setTimeout 把 console.log('setTimeout') 放到 macrotask 队列中
执行 aync1() 输出 ‘async1 start' 和 'async2' ,把 console.log('async1 end') 放到 micro 队列中
执行到 promise ,输出 'promise1' ,把 console.log('promise2') 放到 micro 队列中
执行 console.log('script end'),输出 ‘script end'
macrotask 执行完成会执行 microtask ,把 microtask quene 里面的 microtask 全部拿出来一次性执行完,所以会输出 'async1 end' 和 ‘promise2'
开始新一轮的事件循环,去除执行一个 macrotask 执行,所以会输出 ‘setTimeout'
个人设置案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./css/bootstrap.min.css" />
<style>
.form-select {
width: 103px;
display: inline-block;
}
.col-form-label {
text-align: right;
}
.figure-img {
width: 100px;
height: 100px;
cursor: pointer;
}
#upload {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1 class="p-5">个人设置</h1>
<form class="col-6">
<div class="row mb-3">
<label class="col-form-label col-3">昵称:</label>
<div class="col-9">
<input class="form-control col-9" type="text" name="nickname" />
</div>
</div>
<div class="row mb-3">
<label class="col-form-label col-3">籍贯:</label>
<div class="col-9">
<select class="form-select col-4" name="province">
<option value="">--省--</option>
</select>
<select class="form-select col-4" name="city">
<option value="">--市--</option>
</select>
<select class="form-select col-4" name="area">
<option value="">--区--</option>
</select>
</div>
</div>
<div class="row mb-3">
<label class="col-form-label col-3">头像:</label>
<div class="col-9">
<input class="form-control col-9" type="hidden" name="avatar" />
<figure class="figure">
<input type="file" id="upload" />
<img src="https://yanxuan-item.nosdn.127.net/12a882699bd531a1bd428bffe1989525.jpg"
class="figure-img img-fluid rounded" alt="..." />
<figcaption class="figure-caption">修改头像</figcaption>
</figure>
</div>
</div>
<div class="row mb-3">
<label class="col-3"></label>
<div class="col-9">
<button class="btn btn-primary">保存</button>
</div>
</div>
</form>
</div>
<script src="./lib/bootstrap.min.js"></script>
<script src="./lib/axios.js"></script>
<script src="./lib/form-serialize.js"></script>
<script>
// 在线接口文档:https://www.showdoc.com.cn/1834761734600444/8477923164668939
// 配置请求根路径
axios.defaults.baseURL = 'http://ajax-api.itheima.net'
// 昵称
let nameInput = document.querySelector('[name=nickname]')
// 头像
let upload = document.querySelector('#upload')
let avatarImg = document.querySelector('.figure img')
// 隐藏域
let hiddenAvatar = document.querySelector('[name=avatar]')
// 省市区
let form = document.querySelector('form')
let provinceSelect = document.querySelector('[name=province]')
let citySelect = document.querySelector('[name=city]')
let areaSelect = document.querySelector('[name=area]')
// ========================================= 渲染展示功能 =========================================
// 获取数据结果,展示到页面中
;(async function () {
const {data: res} = await axios.get('/api/settings')
console.log(res);
const {nickname, area, avatar, city, province} = res.data
// 昵称
nameInput.value = nickname
// 省市区
// 2、获取省市区的数据据并渲染
// 2.1获取省渲染
const {data: provinceRes} = await axios.get('/api/province')
console.log(provinceRes);
const provinceStr = provinceRes.data.map(item => `<option value="${item}">${item}</option>`).join('')
provinceSelect.innerHTML = `<option value="">--省--</option>${provinceStr}`
// 获取市 渲染
const {data: cityRes} = await axios.get('/api/city',{
params:{
pname: province
}
})
console.log(cityRes);
const cityStr = cityRes.data.map(item => `<option value="${item}">${item}</option>`).join('')
citySelect.innerHTML = `<option value="">--市--</option>${cityStr}`
// 地区
const {data: areaRes} = await axios.get('/api/area',{
params:{
pname: province,
cname: city
}
})
console.log(areaRes);
const areaStr = areaRes.data.map(item => `<option value="${item}">${item}</option>`).join('')
areaSelect.innerHTML = `<option value="">--区--</option>${areaStr}`
provinceSelect.value = province
citySelect.value = city
areaSelect.value = area
// 头像
avatarImg.src = avatar
hiddenAvatar.value = avatar
})()
// ========================================= 省市切换功能 =========================================
// 省切换
provinceSelect.addEventListener('change', async function() {
// 获取当前选择的省
// console.log(provinceSelect.value);
// 1、获取市的数据
// 获取市 渲染
const {data: cityRes} = await axios.get('/api/city',{
params:{
pname: this.value
}
})
console.log(cityRes);
const cityStr = cityRes.data.map(item => `<option value="${item}">${item}</option>`).join('')
citySelect.innerHTML = `<option value="">--市--</option>${cityStr}`
areaSelect.innerHTML = `<option value="">--区--</option>`
})
// 市切换
citySelect.addEventListener('change', async function() {
// 地区
const {data: areaRes} = await axios.get('/api/area',{
params:{
pname: provinceSelect.value,
cname: this.value
}
})
console.log(areaRes);
const areaStr = areaRes.data.map(item => `<option value="${item}">${item}</option>`).join('')
areaSelect.innerHTML = `<option value="">--区--</option>${areaStr}`
})
// ========================================= 头像上传功能 =========================================
// 点击图片,模拟点击文件域
avatarImg.addEventListener('click', function() { upload.click() })
// 头像change
// 1、注册change事件
// 2、获取文件 参数 数据 this.files[0]
// 3、创建 fd 实例 FormData
// 4、fd.append('', )
// 5、发送数据
upload.addEventListener('change', async function() {
console.log(this.files[0]);
const fd = new FormData()
fd.append('avatar', this.files[0])
const {data: res} = await axios.post('/api/file', fd)
console.log(res);
// 6、更改图片
avatarImg.src = res.data.url
// 7、将地址保存在隐藏域
hiddenAvatar.value = res.data.url
})
// ========================================= 保存功能 =========================================
// 1、监听 提交事件
// 2、阻止默认行为
// 3、处理数据
// 4、发送请求
form.addEventListener('submit', async function(e) {
e.preventDefault()
// 因为 serialize 不能获取 img 标签上的数据 所以需要 将 img 上的 src 值 保存在input 的value中才能获取
// 又因为这个input 框仅仅是用来保存数据的 所以 需要隐藏 隐藏域
const data = serialize(this, {hash: true})
console.log(data);
const {data: res} = await axios.put('/api/settings', data)
console.log(res);
alert(res.message)
})
</script>
</body>
</html>