【vue设计与实现】响应系统的作用与实现 8-过期的副作用

竞态问题通常在多进程或多线程编程中被提及,前端很少讨论到,但在日常工作中会遇到与竞态问题相似的场景,例如:

let finalData
watch(obj, async ()=>{
	// 发送并等待网络请求
	const res = await fetch('/path/to/request')
	// 将请求结果复制给data
	finalData = res
})

上面的代码,咋一看没什么问题。但仔细思考会发现这段代码会发生竞态问题。如果在修改obj对象的某个字段值,会导致回调函数执行并发送第一次请求A,要是在请求A的结果返回之前,对obj对象的某个字段进行了第二次修改,这会导致发送第二次请求B。此时请求A和B都在进行中,哪一个请求会先返回结果,我们不确定。如果请求B先于请求A返回结果,就会导致最终finalData中存储的是A请求的结果。但是希望变量finalData存储的值应该是由请求B返回的结果。

其实解决思路可以是这样把后一次请求B的结果视为“最新的”,把请求A的视为“过期”的,其请求结果也就视为无效的。通过这种方式可以避免竞态问题导致的错误结果。其实就是需要一个让副作用过期的手段。下面就拿Vue.js作例子,看看Vue.js是怎么解决这个问题的:
在Vue.js中,watch函数的回调函数接收第三个参数onInvalidate,这是个函数,类似于时间监听器,我们可以使用onInvalidate函数注册一个回调,这个回调函数会在当前副作用函数过期时执行:

watch(obj, async(newValue, oldValue, onInvalidate)=>{
	// 定义一个标志,代表当前副作用函数是否过期,默认为false,代表没有过期
	let expired = false
	// 调用 onInvalidate() 函数注册一个过期回调
	onInvalidate(()=>{
		// 当过期时,将expired设置为true
		expired = true
	})

	// 发送并等待网络请求
	const res = await fetch('/path/to/request')
	// 只有当该副作用函数的执行没有过期时,才会执行后续操作
	if (!expired){
		finalData = res
	}

})

那么onInvalidate的原理是什么?其实就是在watch内部每次检测到变更后,在副作用函数重新执行前,会调用通过onInvalidate函数注册的过期回调,如下面代码所示:

function watch(source, cb, options = {}){
	let getter
	if(typeof source === 'function'){
		getter = source
	}else{
		getter = () => traverse(source)
	}
	
	let oldValue, newValue

	// cleanup 用来存储用户注册的过期回调
	let cleanup
	// 定义 onInvalidate函数
	function onInvalidate(fn){
		// 将过期回调存储到cleanup中 (fn)
		cleanup = fn
	}
	const job = ()=>{
		newVlue = effectFn()
		// 在调用回调函数cb之前,先调用过期回调
		if(cleanup){
			cleanup()
		}
		//将 onInvalidate作为回调函数的第三个参数,以便用户使用
		cb(oldValue, newValue, onInvalidate)
		oldValue = newValue
	}
	
	const effectFn = effect(
		()=> getter(),
		{
			lazy: true,
			scheduler: ()=>{
				if(options.flush === 'post'){
					const p = Promise.resolve()
					p.then(job)
				}else{
					job()
				}
			}
		}
	)

	if(options.immediate){
		job()
	}else{
		oldValue = effectFn()
	}
	
}

这里关键点在job函数内,每次执行回调函数cb之前,先检查是否存在过期回调,如果存在,则执行过期回调函数cleanup。最后把onInvalidate函数作为回调函数的第三个参数传递给cb,以便用户使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值