js异步与同步
# 一、为什么JavaScript是单线程?
javascript语言的最大的特点之一就是单线程 ,意思就是 :只能同一时间执行同一段代码。就比如说: 一句循环代码需要5s来执行 ,那么后面所有的代码都要等着我这个循环结束才能继续执行 ,就是同一时间只能做一件事情
如果有多件事件就必须排队 后面的事情必须等前面的事情完成后才能执行
这种单线程的模式的好处就是实现起来比较简单 执行环境相对单纯 坏处是 只要有一个任务耗时很长 后面的任务都必须排队等待 会拖延整个程序的执行 如: 浏览器无响应(假死) 往往就是因为某一段代码长时间运行 (如死循环) 导致整个页面都卡在了这个地方 其他代码无法执行
那么,为什么JavaScript不能像Java一样有多个线程呢?
这是因为跟这门语言的用途有关。JavaScript作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM。这就决定了它只能是单线程 ,否则会带来很多复杂的同步问题。为了避免复杂性,这个特性我相信将来也不会改变。
如: 假设JavaScript同时有两个线程 一个线程在删除一个元素 另一个线程又在这个元素上添加内容 >那么这个时候浏览器该如何处理呢? 因为不能确定在同时执行这两段代码时 删除元素一定在添加内容之后 如在之前就会报错 所以 为了避免复杂性 从一诞生 JavaScript就是单线程的
最新的HTML5提出了Web Worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程控制,不能操作DOM。所以JS还是单线程。
# 二、 任务队列
我们把需要执行的代码看成一个个任务,JavaScript中 所有的任务可以分为两种,同步任务(sknchronous),异步任务(asynchronous)。
同步任务指: 在主线程上排队执行的任务 只有前一个任务执行完毕后 才能执行下一个任务
。
异步任务指: 不进入主线程 而进入 任务队列(task queue)的任务 等主线程的任务全部执行完成后 主线程会通过event loop(事件循环) 去询问任务队列中是否有可以被执行的任务了 如果有可以被执行的任务 这个时候这个任务就会被放进 主线程执行
。
下面是它们的运行机制:
所有同步任务都在主线程上,形成一个执行栈
主线程之外还有一个“任务队列”,只要异步任务有了运行结果 ,就在任务队列中放一个事件
当执行栈中所有的任务执行完了,就去看看任务队列中有没有需要执行的事件 ,如果有的话,就结束它们的等待,进入执行栈 ,开始执行。
主线程不断重读上面三步
(这里还是单线程,只是多了一个任务队列)
# 三、Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。这是计算机系统的一种运行机制。 JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。
上图中,主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。
# 四、setTimeout 到底是同步还是异步的 ?
除了放置异步任务的事件,"任务队列"还可以放置定时事件。 setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。 需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
综上所属 setTimeout是单线程,类似异步,但不是异步 。
setTimeout并不是异步的 而是JavaScript在执行的时候 会将setTimeout放入任务队列 等待主线程的执行(不阻塞主线程)全部执行完成后 再通过event loop去询问任务队列中是否有可执行的代码 再继续放入主线程中执行 故产生了异步的假象。
# 五、异步的三种实现方式
1) 回调函数
回调函数不一定是异步 , 但异步一定有回调函数
2) 事件
3) promise 承诺对象
2
3
4
同步:按顺序执行正常逻辑,其中一个逻辑卡了,就会阻塞,让下面的代码进行等待。
异步:不会阻塞 卡了 就让下面的程序执行 不卡了再执行,不会按照想象中的顺序执行
# 六、常见异步:
网络请求异步 (网络快(先回调)的先返回结果)
Promise:解决 地狱回调 实现函数式编程(一步一步,也就是同步)
# 七、使用promise的作用:
- 下面的代码需要上面的代码提供结果
- 网络请求2需要网络请求1的结果
- 把异步处理为同步
# 八、三个状态:
更多关于promise
,查看ES6 (opens new window)
1.pending:(待定)初始状态
2.fulfilled: (实现) 操作成功
3.reject(拒绝):操作失败
var promise = new Promise(传入一个函数);
var promise =new Promise(function (resolve,reject){
if(//异步操作成功){
resolve(val); //改变状态 触发then()
}else{
reject(error);//改变状态 触发catch()
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当状态改变,成功就会触发then() 失败触发catch()
Promise.all 全部成功才返回
Promise.race 谁先成功谁返回
# 九、**async/await **
(Promise的打包,实质上就是Promise)
async 加在函数前面
async function f1(){
return 'abc'; //return Promise.resolve('abc')
//**自动包装成promise对象 等价于 return Promise.resolve('abc');**
和return new Promise((resolve)=>{
**resolve('abc')**
**})**
**}
2
3
4
5
6
7
8
9
10
11
12
13
**
async返回一个Promise对象
await放在函数前 等待该函数执行完毕后再往下执行 阻塞 跳过执行 但是一旦错误 后面的代码就不会执行 所以就要用 try/catch
await后面是一个promise才会生效。
**try{**
var c= await f3() //正常执行 就不执行catch 错误执行 catch
**}catch(e){**
dosomething
}
2
3
4
5
6
7
8
9
案例:
async intoNum(filterList) {
for (var i = 0; i < filterList.length; i++) {
await this.GetProduceNum(filterList[i].id)
filterList[i].num = ''
}
//这里不用异步 会拿不到里面的值
for (let j = 0; j < filterList.length; j++) {
filterList[j].num = this.proceNum[j]
}
this.filterTypeArrayList = filterList
console.log(this.filterTypeArrayList)
},
GetProduceNum(stationId) {
return new Promise((resolve, reject) => {
uni.request({
url: `${this.$g.ip}/data/querystationinfobytime`,
method: "POST",
data: {
startTime: this.Sdate,
stationType: `${this.$store.state.StationTypeInfo}`,
endTime: this.Edate,
},
header: {
authorization: 'Bearer ' + uni.getStorageSync('token')
},
success: (res) => {
let filterType=res.data.data.filter(val=>{
return val.stationId==stationId
})
console.log(filterType)
this.proceNum.push(filterType[0].info.length)
console.log(this.proceNum)
resolve('suc');
},
fail: (err) => {
reject('err')
console.log(err)
}
})
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 十、多个await的执行顺序?
function test1() {
console.log(111);
}
function test2() {
console.log(222);
}
async function test3() {
await test1();
await test2();
}
test3();
2
3
4
5
6
7
8
9
10
11
这个执行起来很容易理解,输出结果为111、222。 那么下边改造一下:
function test1() {
setTimeout( ()=> {
console.log(111);
}, 1000);
}
function test2() {
console.log(222);
}
async function test3() {
await test1();
await test2();
}
test3();
2
3
4
5
6
7
8
9
10
11
12
13
输出的结果为222、111。那么问题来了,为什么会造成这样的结果?如果我们想要让执行顺序依然为111、222怎么做?
await等待的东西分两种情况,promise和非promise,遇到promise会阻塞下边的代码,遇到非promise的会直接根据情况进行执行。
setTimeout是异步,但不是promise,没有经过promise包装是无法await
为同步任务,await后面是一个promise才会阻塞
改造了一下代码:
把test1的return封装加一层promise
function test1() {
return new Promise(resolve => {
setTimeout(() => {
console.log(111);
resolve();
}, 2000)
})
}
function test2(res) {
setTimeout(() => {
console.log(222);
}, 1000);
}
async function test3() {
await test1();
await test2();
}
test3();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最终输出111、222,代码的执行顺序如下:
执行test3(),执行test1(),遇到promise,阻塞代码,此时test2()不执行,运行test1(),等待2s,输出111,运行resolve(),执行test2(),等待1s,输出222。
对于匿名箭头立即执行函数无法使用async await,无法生效
await (()=>{
return
new Promise(resolve => {
setTimeout(() => {
console.log("三秒后执行");
resolve();
}, 3000)
})
})()
2
3
4
5
6
7
8
9