js中的异步

天生同步

js一开始是被设计出来用于进行dom操作,如果存在多个线程对同一个dom进行操作则可能存在部分操作无效化的问题,最后可能只采纳一个线程对dom的操作,简单起见,js被设计成了单线程,这样就避免了复杂的线程操作,但是js就没有办法做多线程的操作了。

如何异步

虽然js是单线程的,但是页面是多线程的,页面的线程有:
1.GUI渲染线程
负责渲染浏览器界面。
2.JS引擎线程
主线程负责解析Javascript脚本,运行同步代码。
3.事件触发线程
处理用户操作事件。
4.定时触发器线程
setInterval与setTimeout所在线程。
5.http请求线程
用于向外部服务器发送请求,每次请求都会新开一个线程。
7.轮询处理线程*
用于分发主线程提交的异步事件到对应的线程,以及维护事件队列。

有了这些基础,js就可以做一些异步的操作了。Js代码在执行中会把同步的函数放入到同步任务栈中,栈内函数依次执行,执行完后退栈,遇到异步事件时会把事件交由轮询处理线程,轮询处理线程分发给对应的线程处理,比如说定时器会交给定时触发器线程处理,http请求会交给http请求线程处理,事件处理完成后对应的线程通知轮询处理线程,轮询处理线程会把事件及其回调加入到事件队列中,当同步任务栈清空时,主线程通知轮询处理线程,轮询处理线程会从事件队列中获取队首的事件,如果该事件没有回调函数则销毁该事件,否则将该事件交由主线程入栈执行,执行完成后函数出栈,主线程进行下一个循环,这个过程叫事件循环,事件循环会持续到任务队列清空。

有这样一个问题

有这样一段代码:

var data='nothing'

  ajax({
  	async:true,
  	success:(res)=>{
  		data=res.data
  	}
  })

  console.log(data)

毫无疑问,打印出来的是nothing,因为js解析到ajax那一块时会把时间丢给对应的线程处理,没等它处理完对data赋值就执行console,在ajax回调函数执行前是拿不到服务器返回的数据的,这个问题可能困扰过一些初学的新手。

如何解决

最简单的方法就是async设置为false,但是这样做会导致代码执行到这里是主线程被挂起,知道请求完成,此时可能造成页面白屏或者用户无法操作页面,用户体验极差。
或者我们可以改一下代码:

function getData(cb){
  	 ajax({
  	async:false,
  	success:(res)=>{
  		cb(res.data)
  	}
  })
  }

 function logData(data){
 	 console.log(data)
 }

getData(logData)

使用回调函数的形式,我们可以在不阻塞页面的情况下获取了异步代码中的值。核心理念就是先定义,需要时再执行。

新的需求来了

有三个请求, 现在要求后面一个请求要带上前面的请求的结果。改一下代码:

function getData(cb1,cb2){
  	 ajax({
  	success:(res)=>{
  		cb1(res.data,cb2)
  	}
  })
  }

 function getData2(data,cb){
 	  ajax({
 	  	data:data
  	success:(res)=>{
  		cb(res.data)
  	}
  })
 }

 function getData3(data){
 	  ajax({
 	  	data:data
  	success:(res)=>{}
  })
 }

getData(getData2,getData3)

此时需要不断的传递回调函数,写大量的代码,不直观还容易出错,这就是人们所说的‘回调地狱’。

promise来了

es6中我们有了promise这一强大的api解决了es5中回调地狱的问题。上面的代码可以改为:

promise.resolve()
       		.then(()=>{
       		return new promise((resolve)=>{
       			ajax({
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	})
       	.then((data)=>{
       		return new promise((resolve)=>{
       			ajax({
       				data:data,
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	})
       	.then((data)=>{
       		return new promise((resolve)=>{
       			ajax({
       				data:data,
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	})

这样写代码变得直观一点了,原来的一层层的套娃变成了一条调用链,如果要增加环节,只需要简单的复制一份代码改改就可以了,也容易维护,promise结合all和rush这两个api可以完成日常开发中的大多数需求。但是仍然存在缺点:链式调用之间跨环节传参麻烦而且代码仍然不够简洁。

终极方案

这个方案毫无疑问就是async/await,其实这两api就是promise.resolve()和then()的语法糖,借助他们,我们的异步代码会变得非常直观而且容易维护,把上面的代码改一下:

function getPromise1(){
       		return new promise((resolve)=>{
       			ajax({
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	}

       		function getPromise2(data){
       		return new promise((resolve)=>{
       			ajax({
       				data:data,
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	}

       		function getPromise3(data){
       		return new promise((resolve)=>{
       			ajax({
       				data:data,
       				success:(res)=>{
       					resolve(res.data)
       				}
       			})
       		})
       	}

       	async function asyncFn(){
       		let data1=await getPromise1()
       		let data2=await getPromise1(data1)
       		let data3=await getPromise1(data2)
       	}

       	asyncFn()

这样子直接把套娃和链条分割成了一块一块的代码,可读性和可维护性非常高,而且可以根据需求调整asyncFn函数内await 的执行顺序和传参,完美的解决了上面提出的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值