最近工作,与原生app混合开发。简言之,就是在原生app的webview中开发业务。其中碰到了一个坑点。
由于页面有接口鉴权,我们需要调用原生app通过jsBridge注入的方法(GET_TOKEN),获取账户token。
(以下代码均做了简化,非真实业务代码)
function GET_TOKEN() {
return new Promise(resolve => {
if (BROWSER.isClient) {
WinJSRdy(_ => window.WinJSBridge.call('authtoken', 'getauthtoken', ({ response: { authToken } }) => {
resolve(authToken)
}))
} else if (isDEV) {
resolve(DEV_TOKEN)
} else {
resolve('')
}
})
}
最开始,并没有多想,封装了fetch方法
function REQUEST_WITH_TOKEN(url) {
return GET_TOKEN().then(token=>fetch(url,{headers: { Authorization: token }}))
}
但是当我们这样调用
REQUEST_WITH_TOKEN('url1')
REQUEST_WITH_TOKEN('url2')
REQUEST_WITH_TOKEN('url3')
发现只有第一次会执行。原来客户端做了防抖处理,在短时间内多次调用客户端的方法,客户端会只执行一次。
😂😂😂
只能想办法解决了。最先想到的就是将多次请求依次放入.then中,但这样丑且改变了业务逻辑,所以放弃了。
此时我想到了react源码里经常使用的队列思想,用在此处,可以将多次请求放入一个队列,待token获取完后,按先进先出,依次执行。
首先写一个调度器
let _token = ''
let isGetting = false
let scheduler = {
queue: [],
next: val => {
while (scheduler.queue.length > 0) {
let firstFn = scheduler.queue.shift()
firstFn(val)
}
isGetting = false
_token = ''
},
listen: callback => {
scheduler.queue.push(callback)
},
}
接着生成队列
if (_token) {
return fetch(url,{headers:{Authorization:_token}})
} else {
let hasPromise = new Promise(resolve => {
scheduler.listen(token => {
resolve(token)
})
}).then(_token => {
return fetch(url,{headers:{Authorization:_token}})
})
if (!isGetting) {
isGetting = true
GET_TOKEN().then(token => {
_token = token
scheduler.next(token)
})
}
return hasPromise
}
当第一次请求发起时,_token
为空,生成hasPromise
,这个Promise通过scheduler
注册一个回调,监听下一个token产生时resolve(token)
,同时isGetting
为false,说明没有去取token,此时调用GET_TOKEN
在.then
中调用调度器的next
方法,让调度器产生一个token,并执行queue
中的回调,队列执行完后,回归初始状态,清空token
,重置isGetting
为false,其他请求则生成hasPromise
返回。这样短时间内发起的多次请求,只会调用一次GET_TOKEN
方法。
这里的关键是,调度器通过listen
将每一个Promise的resolve
push进了一个队列,把控了resolve调用时机。
react的源码里的高级技巧比比皆是,有时见得识不得,有时识得用不得。一方面的确源码太绕,相对于业务,无法有直观感受,一方面投入链太长,回报又不明朗,很难长期坚持阅读。这次问题的解决,也是长期浸染在react此类问题的文章中,才有的思路。