目录

js异步与同步

# 一、为什么JavaScript是单线程?

javascript语言的最大的特点之一就是单线程 ,意思就是 :只能同一时间执行同一段代码。就比如说: 一句循环代码需要5s来执行 ,那么后面所有的代码都要等着我这个循环结束才能继续执行 ,就是同一时间只能做一件事情 如果有多件事件就必须排队 后面的事情必须等前面的事情完成后才能执行

这种单线程的模式的好处就是实现起来比较简单 执行环境相对单纯 坏处是 只要有一个任务耗时很长 后面的任务都必须排队等待 会拖延整个程序的执行 如: 浏览器无响应(假死) 往往就是因为某一段代码长时间运行 (如死循环) 导致整个页面都卡在了这个地方 其他代码无法执行

那么,为什么JavaScript不能像Java一样有多个线程呢?

这是因为跟这门语言的用途有关。JavaScript作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM。这就决定了它只能是单线程 ,否则会带来很多复杂的同步问题。为了避免复杂性,这个特性我相信将来也不会改变。

如: 假设JavaScript同时有两个线程 一个线程在删除一个元素 另一个线程又在这个元素上添加内容 >那么这个时候浏览器该如何处理呢? 因为不能确定在同时执行这两段代码时 删除元素一定在添加内容之后 如在之前就会报错 所以 为了避免复杂性 从一诞生 JavaScript就是单线程的

最新的HTML5提出了Web Worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程控制,不能操作DOM。所以JS还是单线程。

# 二、 任务队列

我们把需要执行的代码看成一个个任务,JavaScript中 所有的任务可以分为两种,同步任务(sknchronous),异步任务(asynchronous)。

同步任务指: 在主线程上排队执行的任务 只有前一个任务执行完毕后 才能执行下一个任务

异步任务指: 不进入主线程 而进入 任务队列(task queue)的任务 等主线程的任务全部执行完成后 主线程会通过event loop(事件循环) 去询问任务队列中是否有可以被执行的任务了 如果有可以被执行的任务 这个时候这个任务就会被放进 主线程执行

下面是它们的运行机制:

  1. 所有同步任务都在主线程上,形成一个执行栈

  2. 主线程之外还有一个“任务队列”,只要异步任务有了运行结果 ,就在任务队列中放一个事件

  3. 当执行栈中所有的任务执行完了,就去看看任务队列中有没有需要执行的事件 ,如果有的话,就结束它们的等待,进入执行栈 ,开始执行。

  4. 主线程不断重读上面三步

(这里还是单线程,只是多了一个任务队列)

img

img

# 三、Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。这是计算机系统的一种运行机制。 JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。

img

上图中,主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。

# 四、setTimeout 到底是同步还是异步的 ?

除了放置异步任务的事件,"任务队列"还可以放置定时事件。 setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。 需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

综上所属 setTimeout是单线程,类似异步,但不是异步

setTimeout并不是异步的 而是JavaScript在执行的时候 会将setTimeout放入任务队列 等待主线程的执行(不阻塞主线程)全部执行完成后 再通过event loop去询问任务队列中是否有可执行的代码 再继续放入主线程中执行 故产生了异步的假象。

# 五、异步的三种实现方式

     1) 回调函数 
             回调函数不一定是异步 , 但异步一定有回调函数 
     2) 事件
     3) promise 承诺对象
1
2
3
4

同步:按顺序执行正常逻辑,其中一个逻辑卡了,就会阻塞,让下面的代码进行等待。

异步:不会阻塞 卡了 就让下面的程序执行 不卡了再执行,不会按照想象中的顺序执行

# 六、常见异步:

网络请求异步 (网络快(先回调)的先返回结果)

Promise:解决 地狱回调 实现函数式编程(一步一步,也就是同步)

# 七、使用promise的作用:

  1. 下面的代码需要上面的代码提供结果
  2. 网络请求2需要网络请求1的结果
  3. 把异步处理为同步

# 八、三个状态:

更多关于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()

}

})
1
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')**

**})**

**}
1
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

}
1
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)
					}
				})
			})
		},
1
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();
1
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();
1
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();
1
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)
  })
})()
1
2
3
4
5
6
7
8
9
上次更新: 2023/09/05 17:45:42
最近更新
01
关于我
07-14
02
科学上网
11-15
03
OSS+CDN
09-23
更多文章>
极昼青春
买辣椒也用券