Ping.js源码分析及遇到的问题
背景:现在在做的一个业务系统新功能——证件扫描,其中使用到了webRTC中的
getUserMedia
方法,在页面点击扫描按钮的时候,手机浏览器的授权弹窗一直没有弹出来。最开始怀疑是不同手机和浏览器的底层请求权限性能问题,后面连接电脑调试的时候发现进度条一直在加载中,然后查看网络,有一个图片资源一直卡在加载过程中。最后发现就是一个ping.js测试网络联通性的代码导致的。
来看下ping.js的原理,以下是github上的源码
/**
* Creates a Ping instance.
* @returns {Ping}
* @constructor
*/
var Ping = function(opt) {
this.opt = opt || {};
this.favicon = this.opt.favicon || "/favicon.ico";
this.timeout = this.opt.timeout || 0;
this.logError = this.opt.logError || false;
};
/**
* Pings source and triggers a callback when completed.
* @param {string} source Source of the website or server, including protocol and port.
* @param {Function} callback Callback function to trigger when completed. Returns error and ping value.
* @returns {Promise|undefined} A promise that both resolves and rejects to the ping value. Or undefined if the browser does not support Promise.
*/
Ping.prototype.ping = function(source, callback) {
var promise, resolve, reject;
if (typeof Promise !== "undefined") {
promise = new Promise(function(_resolve, _reject) {
resolve = _resolve;
reject = _reject;
});
}
var self = this;
self.wasSuccess = false;
self.img = new Image();
self.img.onload = onload;
self.img.onerror = onerror;
var timer;
var start = new Date();
function onload(e) {
self.wasSuccess = true;
pingCheck.call(self, e);
}
function onerror(e) {
self.wasSuccess = false;
pingCheck.call(self, e);
}
if (self.timeout) {
timer = setTimeout(function() {
pingCheck.call(self, undefined);
}, self.timeout); }
/**
* Times ping and triggers callback.
*/
function pingCheck() {
if (timer) { clearTimeout(timer); }
var pong = new Date() - start;
if (!callback) {
if (promise) {
return this.wasSuccess ? resolve(pong) : reject(pong);
} else {
throw new Error("Promise is not supported by your browser. Use callback instead.");
}
} else if (typeof callback === "function") {
// When operating in timeout mode, the timeout callback doesn't pass [event] as e.
// Notice [this] instead of [self], since .call() was used with context
if (!this.wasSuccess) {
if (self.logError) { console.error("error loading resource"); }
if (promise) { reject(pong); }
return callback("error", pong);
}
if (promise) { resolve(pong); }
return callback(null, pong);
} else {
throw new Error("Callback is not a function.");
}
}
self.img.src = source + self.favicon + "?" + (+new Date()); // Trigger image load with cache buster
return promise;
};
if (typeof exports !== "undefined") {
if (typeof module !== "undefined" && module.exports) {
module.exports = Ping;
}
} else {
window.Ping = Ping;
}
源码十分简单,核心方法ping
中 new 了一个 Image
,然后给img
设置src
为 source + self.favicon + "?" + (+new Date())
,这个source
就是要测试网站联通性的一个网络地址,favicon
是浏览器tab栏上面显示的那个小图标,后面带上时间戳保证不使用本地缓存。判断网站联通性的原理就是利用图片加载没有跨域问题这个点,通过onload方法判断是否可以正常加载图片。如果可以正常加载就是可以访问该网站,否则就是不能。
遇到的问题
图片加载不像网络请求一样可以abort,如果无法联通该网站的情况下,发送出去的图片请求是取消不了的。在我使用webRTC的getUserMedia方法时候无法及时响应权限弹窗。详细问题请查看WebRTC之 getUserMedia 与 document.readyState
解决
超时的时候将 img.src
设置为空即可
if (self.timeout) {
timer = setTimeout(function() {
pingCheck.call(self, undefined);
self.img.src = ''
}, self.timeout); }
评论区