Dec
13
上次讨论Webview同步桥落地方案(https://lrdcq.com/me/read.php/151.htm)时,核心寻找了在js中表现为同步,但是实际为异步的方案,其中同步xhr这个特性(https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests)虽然在部分浏览器中会显示deprecated,但是并没有被ban掉,因此完全可以使用该特性在普通web中完成阻塞式API调用。这里尝试用该方案封装一个sleep函数,既不是for循环算时间这样消耗算力的sleep,也不是await这样的假同步sleep了。
该方案核心逻辑是依托于同步xhr发送api请求,通过service worker拦截请求,并且在worker中完成任务后返回,在此处worker需要完成的即sleep行为即setTimeout。worker的拦截逻辑如下:
- service worker拦截的所有request都会通过fetch事件的FetchEvent返回处理。但是拦截的请求的范围是固定的——既当前域下的所有请求。因此对于我们需要特殊url做拦截的情况,不能改变host,也最好不要改变path,那拦截标记只能在query上或者header中下文章了。
- event.respondWith必须在事件处理过程中同步调用,否则拦截器会认为并没有对request进行任何处理而进入正常网络请求逻辑。不过event.respondWith的入参可以是response对象或者promise,可以在promise中完成异步任务并且后续resolve。
- 返回的response需要保证基础的请求结构,如上面写的statuscode,statustext,content-type,否则浏览器会认为是无法解析的response而抛出异常。
- service worker注册很简单,代码如下:
完成Service Worker准备工作后便是主线程使用了,主线程的sleep方法如下:
- 这里封装了公共的syncRequest方法来将函数调用转换为同步xhr并返回结果,参数与标记均是通过query传输。
- 这里sleep的return没意义因此没有return,实际上作为同步方法,整体设计应该从responseText回传数据并且反序列化为对象。
- 整体流程如下:
【DEMO】整体完成demo:https://lrdcq.com/test/jssleep/。源码未混淆可以直接查看。这个demo实际运行起来目前也有几个问题:
- 实际上目前只有Firefox与Edge上有效运行,Chrome仍然不支持对同步xhr进行serviceworker拦截,这可能是一个未完成的feature(毕竟不合理,并且W3C Web Platform Tests也有这个测试用例https://github.com/web-platform-tests/wpt/blob/master/service-workers/service-worker/fetch-request-xhr-sync.https.html),但是自从2019年chrome上线service worker以来这个问题一直是open状态。
- Service Worker只能在https的网站上运行,务必要注意。
(Firefox中的运行表现)
该方案核心逻辑是依托于同步xhr发送api请求,通过service worker拦截请求,并且在worker中完成任务后返回,在此处worker需要完成的即sleep行为即setTimeout。worker的拦截逻辑如下:
//serviceworker.js
//service配置
const ServerConfig = {
HOST_KEY: 'local_service_worker_command'
};
//api实现
const APIs = {
sleep: function(params, callback) {
let timeout = params.timeout;
setTimeout(function(){
callback(true, {});
}, timeout);
}
}
//注册拦截器
self.addEventListener('fetch', function(event) {
const requestUrl = new URL(event.request.url);
if (('' + event.request.url).match(ServerConfig.HOST_KEY)) {
const querys = requestUrl.searchParams;
const method = querys.get('method');
const params = JSON.parse(querys.get('params'));
if (APIs[method]) {
event.respondWith(new Promise((resolve, reject) => {
APIs[method](params, function(status, results) {
var responseInit = {
status: status ? 200 : 500,
statusText: 'OK',
headers: {
'Content-Type': 'application/json'
}
};
resolve(new Response(JSON.stringify(results), responseInit));
});
}));
}
}
});
- service worker拦截的所有request都会通过fetch事件的FetchEvent返回处理。但是拦截的请求的范围是固定的——既当前域下的所有请求。因此对于我们需要特殊url做拦截的情况,不能改变host,也最好不要改变path,那拦截标记只能在query上或者header中下文章了。
- event.respondWith必须在事件处理过程中同步调用,否则拦截器会认为并没有对request进行任何处理而进入正常网络请求逻辑。不过event.respondWith的入参可以是response对象或者promise,可以在promise中完成异步任务并且后续resolve。
- 返回的response需要保证基础的请求结构,如上面写的statuscode,statustext,content-type,否则浏览器会认为是无法解析的response而抛出异常。
- service worker注册很简单,代码如下:
//sleep.js
//注册worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('serviceworker.js', {scope: './'}).then(function(registration) {
}).catch(function(error) {
console.warn(error);
});
} else {
console.warn('不支持serviceWorker');
}
完成Service Worker准备工作后便是主线程使用了,主线程的sleep方法如下:
//sleep.js
//公共方法:syncRequest
const syncRequest = function(method, params) {
let request = new XMLHttpRequest();
request.open('GET', './?local_service_worker_command=1&method=sleep¶ms=' + JSON.stringify(params), false);
request.send(null);
if (request.status === 200) {
try {
return JSON.parse(request.responseText);
} catch(e) {
return null;
}
} else {
return null;
}
}
//暴露方法sleep
window.sleep = function (timeout) {
syncRequest('sleep', {'timeout': timeout});
}
//使用即sleep(5000)这样调用即可
- 这里封装了公共的syncRequest方法来将函数调用转换为同步xhr并返回结果,参数与标记均是通过query传输。
- 这里sleep的return没意义因此没有return,实际上作为同步方法,整体设计应该从responseText回传数据并且反序列化为对象。
- 整体流程如下:
【DEMO】整体完成demo:https://lrdcq.com/test/jssleep/。源码未混淆可以直接查看。这个demo实际运行起来目前也有几个问题:
- 实际上目前只有Firefox与Edge上有效运行,Chrome仍然不支持对同步xhr进行serviceworker拦截,这可能是一个未完成的feature(毕竟不合理,并且W3C Web Platform Tests也有这个测试用例https://github.com/web-platform-tests/wpt/blob/master/service-workers/service-worker/fetch-request-xhr-sync.https.html),但是自从2019年chrome上线service worker以来这个问题一直是open状态。
- Service Worker只能在https的网站上运行,务必要注意。
(Firefox中的运行表现)