promise应用
promise其实是一个异步的操作, 相当于新开了一个线程去执行Promise中的代码
链式编程的注意点
第三行代码的执行情况只和第二行代码有关。只有第二行代码异常,第三行代码才会调用第二个回调函数
假设p2调用reject()方法,那么需要第二行第二个函数出现异常,第三行t2调用的then方法才会打印失败
假设p2调用resolve()方法,那么需要第二行第一个函数出现异常,第三行t2调用的then方法才会打印失败
同步和异步
同步
执行任务时,必须等待当前任务完成之后,才能执行下一个(排队执行
异步
执行任务时,不必等待当前任务完成之后,直接执行下一个
异步相当于重新开一个线程去执行其他任务
异步的概念
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。
在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。
简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:异步就是从主线程发射一个子线程来完成任务。
promise其实是一个异步的操作
相当于新开了一个线程去执行Promise中的代码
链式编程的注意点
第三行代码的执行情况只和第二行代码有关。只有第二行代码出现了异常,第三行代码才会调用第二个回调函数
假设p2调用reject()方法,那么需要第二行第二个函数出现异常,第三行t2调用的then方法才会打印失败
假设p2调用resolve方法,那么需要第二行第一个函数出现异常,第三行才会打印失败
const p3 = new Promise((resovle,reject)=>{resovle('失败后的数据')})
const t3 = p3.then(()=>console.log(data),()=>console.log(abc))
// 这里resolve则data没有定义,报异常;reject则abc没有定义,报异常=>失败
t3.then(()=>console.log('成功'),()=>console.log('失败'))
平级调用
使用promise进行改进,利用then方法链式编程,实现了平级调用而不是嵌套调用,
/*
1,分三次输出字符串,第一次间隔1秒后输出first,
第二次间隔2秒后输出second,第三次间隔3秒后输出third
考虑使用promise实现
*/
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("First");
resolve();
}, 1000);
}).then(() => {
return new Promise((resolve, reject) => {
// 为了抽取方便,加上return。不写return new也可以,
setTimeout(() => {
console.log("Second");
resolve();
}, 2000);
});
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Third");
resolve();
}, 3000);
});
})
改进为封装函数调用的方法
封装方法中的return是为了返回一个对象,对象就可以继续调用then,如果不return,则会返回undefined,undefined调用then方法就会报错。
调用方法中的return是为了将promise内容抽成对象,对象就可以继续调用then,如果不return,则调用的执行时机难以控制
如果拆开写,则最后一个方法不用写return new,如果前面不写,则会无法控制第二个then的执行时机,不能实现可控连续调用,因此不推荐。
// 封装一个间隔打印的方法
function print(during,value){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(value);
resolve();
}, during);
});
}
print(1000,'first').then(()=>{
return print(2000,'second')
}).then(()=>{
return print(3000,'third')
})
1.async-await
async
概念
异步的意思
作用
可以将同步函数变成异步函数(调用async去修饰函数,返回的就是promise对象)
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。
// 异步函数(函数返回的结果是promise对象
async function f2(){
return 'aaa'
}
var res2 = f2()
console.log(res2)
// Promise {<fulfilled>: 'aaa'}
// aaa存在于Promise的结果属性中
// 打印结果变成了fulfilled状态的promise
res2.then(data=>console.log(data)) // 这样才能拿到aaa
await
概念
等待
作用
可以等待他后面的promise对象,执行完之后,拿到结果属性中的数据(可以理解为promise返回的数据)
注意
await不能单独使用,必须和async连用,必须存在于异步函数中
await后面必须跟一个promise对象,如果不是promise对象,他会自动转成promise对象
await表达式返回的是promise执行后的结果(异步函数结果属性中的值)
间隔打印的await实现
// 封装一个间隔打印的方法
function print(during, value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(value);
resolve();
}, during);
});
}
async function f() { // 必须写在异步函数里面
await print(1000, 'first') // 直接拿到promise对象中的数据
await print(2000, 'second')
await print(3000, 'third')
}
f() // 间隔打印内容
2.事件循环
进程
内存中正在运行的一个程序,每一个应用至少有一个进程
线程
程序的一条执行路径
一个进程至少有一个线程,在进程开始之后,会自动创建一个线程来运行代码,这个线程就是主线程
多线程
一个程序有多条执行路径,每个路径可以执行不同的代码,完成不同的任务(360就是一个多线程程序)
浏览器
一个多进程、多线程的应用程序,它至少开启了三个进程
浏览器进程:页面展示,用户交互,子进程管理
网络进程:主要负责加载网络资源
渲染进程:开启一个渲染主程序,负责执行HTML,CSS和JS
为了避免相互影响,为了减少连环崩溃的几率,浏览器启动后会自动进行多进程
同步任务
在主线程上排队的任务,只有一个任务完成之后,才会通知执行下一个任务
异步任务
不进入主程序,而是进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
事件循环
Event Loop 是js或者node为解决单线程代码执行不阻塞主程序的一种机制(可以理解为异步
事件循环主要负责——执行代码,收集和处理事件,以及执行队列中的子任务
优先级问题
任务没有优先级,但是消息队列是有优先级的
由于浏览器复杂性急剧提升,W3C以及不再使用传统的宏队列和微队列方式
而是由浏览器来决定选取哪一个队列来执行
常见队列
微队列
存放需要最快执行的任务,优先级最高(vip通道
交互队列
存放用户操作产生的事件处理函数,优先级次之
延时队列
存放定时器里定义的回调函数,优先级最低
渲染主程序的执行流程
步骤:(只分析渲染主程序执行全局js代码这一块流程
- 主程序执行script标签下的代码
- 执行到给按钮添加事件代码时,会通知交互线程(异步线程),交互线程会将事件处理函数fn定义好,并监听按钮的点击。
- 一旦用户点击了按钮,交互线程就会把事件处理函数fn放到消息队列当中
- 主线程执行完同步代码之后,就会从消息队列当中拿到fn进行执行
-
- 首先执行的应该是给h1改变内容的赋值代码,再重新绘制页面
- 此时会产生一个新的任务——绘制任务(异步任务);线程会将这个任务添加到消息队列中
- 执行完赋值后,此时继续向下执行就会执行到delay。delay中相当于是一个循环三秒后fn才执行完的死循环。
- 主程序继续从消息队列中拿到绘制任务,执行。执行完之后,用户才能看到内容发生改变
如果在等待的三秒钟里,做了其他操作/点了其他按钮,都需要排队
<h1>xql</h1>
<button>111</button>
<script>
// 获取元素对象
const h1 = document.querySelector('h1')
const btn = document.querySelector('button')
// 演示同步阻塞情况
// 定义一个等待指定时间的方法,类似定时器,但不是定时器,定时器是异步
function delay(duration){
// 先获取开始的时间
const start = Date.now() // 开始的时间
while(Date.now() - start < duration){}
// 每次循环判断时候的时间。对比开始的时候的时间和现在的时间,如果间隔小于指定时间,就等待
}
btn.addEventListener('click',function(){
h1.innerHTML = 'hello world'
delay(3000) // 这行代码阻塞了主渲染程序的执行
})
具体流程
- 渲染主程序开启,执行全局的js代码
- 执行setTimeout(function()的时候,计时线程启动,因为定时是0秒,所以计时线程会将回调函数fn,放到延时队列中,等待执行。
- 主线程接着往下执行,打印222,然后全局的js执行完成
- 主线程再去消息队列当中取出fn执行,再打印111
- 如果计时器等待时间小于阻塞时间,则阻塞结束计时器内容立刻执行
setTimeout(function(){
console.log(111)
},0)
console.log(222)
具体流程
- 渲染主程序开启,执行全局js代码
- 渲染计时器,计时器线程启动,定时是0秒,计时器线程将回调函数fn添加到延时队列
- 主程序接着往下执行,执行promise,会将打印2的函数添加到微队列中
- 主程序接着往下执行,执行打印3,全局js代码执行完成
- 再去消息队列中取出微队列中的函数执行,打印2,然后取出fn,打印1
Promise.resolve()其实是new Promise((resolve,reject)=>{resolve()})的简写方式
promise的resolve()方法能改变代码的优先级,将其放到微队列中,在全局js代码执行结束后优先执行
setTimeout(function(){
console.log(1)
},0)
// 使用promise的resolve()方法去改变代码的优先级
Promise.resolve().then(function(){
console.log(2)
})
console.log(3)