首页 Javascript 正文
309

Fetch实现自定义下载协议

  • yiqingpeng
  • 2018-01-02
  • 0
  •  
ajax和Fetch Api具备基本相同的功能,但是FetchApi的可扩展性更强大,特别是支持Http Header的自由控制,从而可以实现一些ajax不能实现的功能,比如以下代码就是利用fetch自定义了一个分块下载协议,实现数据的分块下载以及断点续传。当然此示例仅仅是供学习研究,并不具备可用性(fetch只有部分浏览器支持+url长度的限制下载不了大文件)
;(function(window, $){
	function H5Downloader(aBtn){
		if ( !fetch ) throw 'Fetch接口未定义!';
		this.aBtn = aBtn || document.createElement('a');
		this.auto = !aBtn;
		this.filename = "";
		this.totalRange = 0;
		this.curRange = 0;
		this.rangeSpan = 3;
		this.dataBuffer = [];
		this.unsortData = [];
		this.lockFlag = false;
		this.lockTimer = 0;
		this.downloadUrl = '';
		this.sessionToken = '';
		this.unit = 'rows';
		this.pause = false;
		this.mode = 'common';
	}
	H5Downloader.prototype.onbegin = H5Downloader.prototype.ondata = H5Downloader.prototype.onend =  function(){
		return this;
	}
	H5Downloader.prototype.showMsg = function(msg){
		alert(msg);
	}
	H5Downloader.prototype.lock = function(expired){
		expired = expired || 200; //默认200ms超时
		if(this.lockFlag) return false;
		this.lockFlag = true;
		this.lockTimer = setTimeout((function(that){
			return function(){that.unlock()};
		})(this), expired);
		return true;
	}
	H5Downloader.prototype.unlock = function(){
		this.lockFlag = false;
		clearTimeout(this.lockTimer);
		return true;
	}
	H5Downloader.prototype.serverError = function(response){
		var that = this;
		response.text().then(function(msg){
			console.log(msg);
			that.showMsg('网络异常,下载中断');
		});
	}
	H5Downloader.prototype.networkError = function(error){
		console.log('There has been a problem with your fetch operation: ' + error.message);
		this.showMsg('网络异常,下载失败!');
	}
	H5Downloader.prototype.stop = function(callback){
		this.pause = true;
		typeof(callback)=='function' && callback();
	}
	H5Downloader.prototype.restart = function(){
		this.pause = false;
		this.getFileData(this.curRange);
	}
	H5Downloader.prototype.requestDownload = function(downloadUrl){
		if(!downloadUrl) throw 'Download Url required!';
		this.downloadUrl = downloadUrl;
		var that=this;
		var myHeaders = new Headers();
		myHeaders.append("X-BBC-Askfor", "download = init");
		myHeaders.append("X-BBC-Range", that.unit+"=*");
		var myInit = {headers:myHeaders};
		fetch(this.downloadUrl, myInit).then(function(response) {
			if(response.ok) {
				var range = RegExp(that.unit+'\s*?\=\s*?(.+?)$').exec(response.headers.get('X-BBC-Range'))[1].replace(/^\s+?|\s+?$/)*1;///rows\s*?\=\s*?(.+?)$/
				that.totalRange = range;
				that.filename = that.getFileName(response);
				that.sessionToken = response.headers.get('X-BBC-SessID');
				that.onbegin();
				if(that.mode=='thread' && window.Worker)
					that.downloadByThread();
				else{
					that.getFileData(0); 
				}
				
			} else {
				that.serverError(response);
			}
		}).catch(function(error) {
			that.networkError(error);
		});
		
	}
	H5Downloader.prototype.getFileData = function(startRange){
		var that=this;
		var myHeaders = new Headers();
		//myHeaders.append("Range", "bytes=5-7");
		myHeaders.append("X-BBC-Askfor", "download = transfer");
		myHeaders.append("X-BBC-Range", that.unit+"="+startRange+"-"+(startRange+that.rangeSpan));
		var myInit = {headers:myHeaders};
		fetch(this.downloadUrl, myInit).then(function(response) {
			if(response.ok) {
				if(that.curRange >= that.totalRange){
					var myBlob = new Blob(that.dataBuffer);
					that.onend();
					that.attach(myBlob, that.filename);
				}else{
					response.blob().then(function(myBlob) {
						that.dataBuffer.push(myBlob);
						that.curRange = startRange+that.rangeSpan;
						if(that.curRange >= that.totalRange){
							var myBlob = new Blob(that.dataBuffer);
							that.onend();
							that.attach(myBlob, that.filename);
							return;
						}
						that.ondata(myBlob);
						!that.pause && that.getFileData(that.curRange);
					});
				}
			} else {
				that.serverError(response);
			}
		}).catch(function(error) {
			that.networkError(error);
		});
	}
	H5Downloader.prototype.serializeProperties = function(){
		var that = this;
		return {
			downloadUrl: that.downloadUrl,
			totalRange: that.totalRange,
			rangeSpan: that.rangeSpan,
			sessionToken: that.sessionToken,
			unit: that.unit
		}
	}
	H5Downloader.prototype.buildTaskData = function (){
		var num = Math.ceil(this.totalRange/this.rangeSpan), startList=[], start=0, attachData = this.serializeProperties();
		for(var i=0; i<num; i++){
			startList.push({startRange: start, attachData: attachData});
			start += this.rangeSpan;
		}
		return  startList;
	}
	H5Downloader.prototype.downloadByThread = function(){
		var taskData = this.buildTaskData(), that=this;
		//console.log(taskData);
		var tdmgr = new ThreadManager(taskData.length, 'worker.js', taskData, function (data){
			//处理成功消息
			var lock = that.lock();
			if(false===lock) throw '下载的数据出现错误';
			that.unsortData.push([data.startRange, data.blob]);
			that.unlock();
			that.ondata(data.blob);
		}, function(error){
			//处理错误消息
			console.log(error);
			
		}, function (){
			//下载完成后对数据进行排序组装
			var unsortData = that.unsortData;
			if(unsortData.length != taskData.length){
				that.showMsg('下载过程中出现错误,请重新下载!');
				return;
			}
			//排序
			unsortData.sort(function(x, y){
				return x[0] -y[0];
			});
			for(var i=0,t=that.unsortData.length; i<t; i++){
				that.dataBuffer.push(unsortData[i][1]);
			}
			var myBlob = new Blob(that.dataBuffer);
			that.onend();
			that.attach(myBlob, that.filename);
			//console.log(unsortData);
			console.log('Finished');
		});
	}
	H5Downloader.prototype.getFileName = function(response){
		return response.headers.get('X-BBC-FileName') || (function(){
			var find = /filename\=\s*?(.+?)$/.exec(response.headers.get('Content-Disposition')); //解析"attachment;filename=my_test.csv"
			return find[1]?find[1].replace(/^\s+?|\s+?$/) : null;
		})() || ((new Date()).getTime() + ".unknow");
	}
	H5Downloader.prototype.buildBlob = function (dataArr){
		return new window.Blob(dataArr); //dataArr是一个包含数据的一维数组
	}
	H5Downloader.prototype.attach = function (blob, filename){
		var url = window.URL.createObjectURL(blob);
		this.aBtn.href = url;
		//this.aBtn.textContent = decodeURIComponent(filename);
		//this.aBtn.title = txt;
		this.aBtn.download = decodeURIComponent(filename);
		this.auto && this.aBtn.click();
		return this;
	}
	H5Downloader.prototype.download = function(url,options){
		options = options || {};
		this.rangeSpan = options['rangeSpan'] || 3;
		this.unit = options['rangeUnit'] || 'bytes';
		this.mode = options['mode'] || 'common';
		this.requestDownload(url);
	}
	
	window.H5Downloader = H5Downloader;
	return H5Downloader;
})(window, $);

$(function(){
	
	var d = new H5Downloader(document.getElementById('download_btn'));//
	$('#stop_btn').click(function(){
		d.stop();
		$(this).hide();
		$('#restart_btn').show();
	});
	$('#restart_btn').click(function(){
		d.restart();
		$(this).hide();
		$('#stop_btn').show();
	});
	d.onbegin = function (){
		console.log('Start Time:'+(new Date()).getTime());
	}
	d.onend = function(){
		var txt = '可以下载了';
		this.aBtn.textContent = txt;
		this.aBtn.title = txt;
		this.aBtn.onclick = null;
		console.log('End Time:'+(new Date()).getTime());
	}
	d.ondata = function(){
		console.log('finished:'+(this.curRange/this.totalRange*100).toFixed(2)+'%');
	}
	//d.download('Easy/Test/testDownload?filename=download.txt', {'rangeUnit':'rows','rangeSpan':3, mode:'threadd'});
	//d.download('Easy/Design/blockdownload?formcode=5a1b741c8ddcf', {'rangeUnit':'rows','rangeSpan':4});
	//d.download('Easy/Design/blockdownload?formcode=59844c9204fea', {'rangeUnit':'rows','rangeSpan':100, mode:'thread'});
});
下载按钮的点击事件:
downloadBtn.onclick = function(){
       fetch('easy/design/requestExport?formcode=59844c9204fea&action=create').then(function(response) {
			response.text().then(function(txt){
					console.log(txt);
			})
	}).catch(function(error) {
		console.log(error);
	});
}

正在加载评论...