目录
一、多线程和异步操作的作用和区别
1.作用
两者都是避免线程因为某个任务阻塞而影响程序执行,提高响应速度和执行效率
2.区别
多线程:
多个线程同时处理任务,每个线程内部的任务还是顺序执行的,缺点就是线程的大量使用会造成系统负担变大,线程之间共享变量可能会造成死锁。
Thread1:taskA--->taskB
Thread2:taskC--->taskD
异步操作:
异步操作主要是是在程序上这样设计,常常采用回调的方式来处理异步得到的结果,设计编写上会复杂一些
主线程MainThread执行下面操作
通俗来讲,主线程执行TaskA,当处理到需要读取数据时,把读取这部分工作交给其他Process,然后自己再执行后面的任务,当Process执行完成时,调用回调函数,然后完成执行TaskA。
这里的Process可以是DMA(DMA技术使得硬件可以直接访问内存,而不需要依赖于CPU的大量中断负载),也可以是其他线程进行操作。
二、JavaScript是单线程的
详细可参考:通用异步编程概念 - 学习 Web 开发 | MDN
JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程。
类比上面的异步操作,Process异步执行过程可以通过 Web workers 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块,一个worker执行一个需要耗时的任务,主线程用于处理交互,这样就避免了阻塞。
三、异步JavaScript的几种实现形式
详细可参考:异步JavaScript简介 - 学习 Web 开发 | MDN
1.callbacks
实际上这种老派callbacks异步编程风格就是设置回调函数,等待任务结束后得到某项数据或操作时要执行什么操作。
(1)比如addEventListener:
testCallBacks(){
var btn01 = document.getElementById("btn01");
btn01.addEventListener('click', () => {
alert('You clicked me!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
}
},
其中addEventListener的第二个参数就是一个回调函数,是异步执行的,只有在对button执行click操作时才会执行回调函数,效果如下:
(2)如果对于异步看的不明显,则可以利用setTimeout来进行测试
testsetTimeout(){
setTimeout(()=>{//3s以后执行回调函数
alert('这是3s之后执行的信息')
},3000)
this.testsetTimeoutInfo='0s'
}
setTimeout(func,time),第一个参数是回调函数,第二个参数是延迟执行的时间,其实这里并不是严格的time时间后执行,而是在time时间后将任务加到主线程的执行队列中,具体的执行时间还是要看主线程的执行情况。
从中可以看出改变testsetTimeoutInfo属性发生在setTimeout的回调函数之前,也就是常见的异步操作过程。
2.Promise风格
Promise是新派异步编程风格代码,其实是把异步过程进行包装,支持更多更高效的操作,其中fetch()就是一个高效的XMLHttpRequest,只需要一个参数——资源的网络 URL,然后返回一个 promise。promise 是表示异步操作完成或失败的对象。可以说,它代表了一种中间状态。
Promise是专门为异步操作而设计的,与旧式回调相比具有许多优点:
Promise可以利用then形成链式执行,将多个异步操作串联起来,把一个操作的结果作为输入传递到下一个操作中,可以将错误放到catch里面进行处理,错误处理更加方便。Promise.all可以将任务数组作为参数,清楚的知道这些任务的执行结果,然后针对这些结果进行下一步操作。
(1)Promise包装异步过程
testpromise(){
let newpromise=new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('0.3s中后执行');
}, 300);
})
newpromise.then((value)=>{
console.log(value)
setTimeout(() => {
console.log('0.6s中后执行');
}, 300);
})
console.log('0s后执行')
}
(2)Pormise.all(taskarr)
当taskarr任务全部执行完后才会执行console.log(val)
testpromiseAll(){
let newpromise1=new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('0.1s中后执行')
resolve('0.1s中后执行');
}, 100);
})
let newpromise2=new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('0.2s中后执行')
resolve('0.2s中后执行');
}, 200);
})
let newpromise3=new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('0.3s中后执行')
resolve('0.3s中后执行');
}, 300);
})
let promiseArr=[newpromise1,newpromise2,newpromise3]
Promise.all(promiseArr).then((val)=>{
console.log(val)
})
}
3.通过时间推迟执行代码实现异步
详细可参考:合作异步JavaScript: 超时和间隔 - 学习 Web 开发 | MDN
(1)setTimeout()
[指定时间后执行一段代码,上面已经使用到了]
要注意一下时间间隔参数,(即使是0时)并不是立刻执行,指定的时间(或延迟)不能保证在指定的确切时间之后执行,而是最短的延迟执行时间。在主线程上的堆栈为空之前,传递给这些函数的回调将无法运行。
testsetTimeout0(){
setTimeout(()=>{
console.log('设置setTimeout的参数time=0')
},0)
for(let idx=0;idx<5;idx++){
console.log(idx)
}
}
(2)setInterval()
[以固定时间间隔,重复执行一段代码]
let i = 1;
setInterval(function run() {
console.log(i);
i++
}, 100);
注意:如果run()函数执行了40ms,则在执行完成60ms以后将执行下一次的run函数
如果想要严格的每100ms执行一次run则可以采用递归setTimeout()的方式实现:
testsetTimeout1(){//递归setTimeout()
let i = 1;
setTimeout(function run() {
console.log(i);
i++;
if(i<20){
setTimeout(run, 100);
}
}, 100);
},
4.async/await
其实async和await是基于promise的语法糖,本质上还是promise,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码。
使用 async 关键字,把它放在函数声明之前,使其成为 async function。异步函数是一个知道怎样使用 await 关键字调用异步代码的函数,async修饰的函数返回值是promise
需求:“await执行完成以后才会打印”这句话在3s后执行之后打印
(1)如果不使用await的效果
getpromise(){
let newpromise=new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('3s后执行')
resolve('3s后执行');
}, 3000);
})
return newpromise
},
testAsyncAndAwait(){
this.getpromise()
console.log("await执行完成以后才会打印")
}
下面使用async/await修饰以后(符合要求)
getpromise(){
let newpromise=new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('3s后执行')
resolve('3s后执行');
}, 3000);
})
return newpromise
},
async testAsyncAndAwait(){
await this.getpromise()
console.log("await执行完成以后才会打印")
}
完全可以通过调用 await Promise.all() 将所有结果返回到变量中,就像同步代码一样,避免了then的调用,更简洁了。