用any-loader封裝jQuery的XHR —— 隨便寫着玩系列 原 薦

哎,都說沒人用JQuery啦,叫你別寫這個。

其實我也是好高騖遠使用過npm上某個和某個很出名的XHR庫,嗯,認識我的人都知道我喜歡噴JQ,以前天天噴,見面第一句,你還用JQ,趕緊丟了吧。但我也是用過了npm那些腦殘玩意,才知道,艾瑪,其實$.ajax還不錯,於是乎,我又開始天天噴那些某某知名類庫。今天到處都能看到jQuery老矣的話題,飯否(不是jQuery能飯否,是用jQuery的人能飯否)?嗯,其實我覺得jQ還是可堪一用的,prop、attr、data,以及$.fn的擴展,這些方面還是很實用的,特別是基於此結合React.js用來做Web組件使用。

嗯,是的,自從2016年折騰過了一年用Reactjs大前端化(全站前後臺都用),到今天爲止我始終只是把React.js作爲組件庫來使用。徹底的純前端化,所要解決的難題,太多太多,有一些問題可能都超越目前的技術極限。做網站做項目,不是一個純粹的技術理想化的事情。傳統服務器端輸出有着大量的技術積累,框架積累,能夠迅速的解決項目需求,滿足市場發展需要,能確保公司、資金、項目、市場良性的互動。對任何團隊,就算用antd這些全家桶,仍然要求團隊的前端成員都有一年以上組件化開發的基礎硬素質要求,而且還需要準備足夠多的基礎Component,不管antd多香多美好,符合你項目和行業需求的組件必然是高度定製化的。

今天看似各種大全,全家桶,界面組件庫,嗯,似乎做網站,手機應用會越來越簡單了,但我想說的是,少年,你還是太young了,直到出現某個大機構、團隊提供AI自動化代碼構成爲止,編程的世界只會越來越繁雜,這些UI全家桶,只是逼着大家把用戶體驗、用戶交互又推上了一個行業新高度,程序猿攻城獅每天的工作不變,但是代碼層面要求控制的粒度只會更細,用戶交互的體驗感,只會越來越奇葩。這世界本就不存在某個框架或者類庫,橫空出世就讓整個行業一片死寂,就像當年ROR,從1.0版本到後來的版本,複雜度呈幾何倍增長,敏捷、輕量化也都是過去的幻影,最後發現那只是一種奢望,這是必然的,你控制的粒度越細,提供更多方便直觀的接口和更高自由度的編碼區間,實際項目代碼的繁雜程度只會相應的成倍的增長。

剛開始,你或團隊,靠着所謂先進工具,得到了生產力上的短時間提升,客戶一片讚美,老闆對你讚賞有加,提拔到關鍵崗位,給你管理職權,給你更多人力,管理更多項目,你也理所當然的將先進工具進行廣泛的使用和傳播。可這纔是埋下了你或團隊後來的敗筆,後來的故事,往往都是,隨着使用越多,各種曾經不曾有過的,不曾預期的問題都出現了,當然的,必然的,因爲你們的大量廣泛使用,形成自己的需求。最後各個項目紛紛暴雷,項目成員各自甩包,等等,最後,往往犧牲的就是你,背鍋的就是你,因爲是你最先帶頭搞的,也是你積極推廣的,你看果然出事了吧,我早說了不行——你的競爭對手如是說。人生就是這樣了,所謂新技術,新框架的學習熱情慢慢逝去,個人生活也變得豐富多彩了,有老婆孩子了,要相夫教子了,要還房貸,maybe我應該轉做管理吧,慢慢轉型吧,所以技術是朝陽行業,讓年輕人追去吧。

哎,怎麼跑題跑這麼遠了呢,真是,年紀大了,得話癆病了,好以下回歸正題。

因爲要照顧整體團隊的前端技術層次問題,所以我們還是選擇以jQuery的ajax作爲一個切入點。

基礎準備

首先容我安利一番,什麼是 any-loader?什麼,你不喜歡看字?好好

碼雲倉庫:https://gitee.com/janpoem/any-loader

基本的封裝

import $ from 'jquery';
import _ from 'lodash';
import Loader from './Loader'; 
// 這裏我把源代碼放到本地,因爲我們的開發環境裏面webpack的編譯環境比較老,babel 6的版本,babel-loader 還將 node_modules 排除了,所以放到本地,進行一些修改。後面 0.0.2版本會着手解決此問題。

import defineProps from 'define-props';

// 請求封裝類
export class JQueryXhrRequest {
	
	id = '';
	
	url = '';
	
	method = 'get';
	
	data = {};
	
	dataType = 'json';
	
	cache = false;
	
	constructor(args) {
		if (typeof args === 'function') args = args();
		if (args instanceof JQueryXhrRequest)
			return args;
		if (_.isString(args)) args = {url: args};
		if (!_.isObjectLike(args)) args = {};
		
		this.url = _.toString(args.url) || '';
		this.method = _.toLower(_.trim(args.method)) || 'get';
		this.data = args.data || {};
		
		this.dataType = _.toLower(args.dataType) || '';
		this.cache = false;
	}
	
	getConfig() {
		return {
			url     : this.url,
			method  : this.method,
			date    : this.data,
			cache   : this.cache,
			dataType: this.dataType
		};
	}
}

// 響應的封裝類
export class JQueryXhrResponse {
	
	request = null;
	error = null;
	
	readyState = 0;
	statusCode = 0;
	statusText = '';
	
	text = '';
	json = null;
	
	xhr = null;
	
	constructor(request) {
		this.request = request;
	}
	
	bindResponse(jqXHR, error) {
		this.xhr = jqXHR;
		
		this.readyState = jqXHR.readyState;
		this.statusCode = jqXHR.status;
		this.statusText = jqXHR.statusText;
		this.text = jqXHR.responseText;
		this.json = jqXHR.responseJSON;
		
		return this;
	}
}

// Loader 的實現
export class JQueryXhrLoader extends Loader {
	
	newInput(input) {
		return new JQueryXhrRequest(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {JQueryXhrResponse}
	 */
	newOutput(input, output) {
		return new JQueryXhrResponse(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {Promise<any>}
	 */
	doLoad({input, output}) {
		return new Promise((resolve, reject) => {
			$.ajax(input.getConfig())
			 .done((jqData, textStatus, jqXHR) => {
				 output.bindResponse(jqXHR);
				 resolve();
			 })
			 .fail((jqXHR, textStatus, errorThrown) => {
				 const error = new Error('jQuery ajax load failed!');
				 output.bindResponse(jqXHR, error);
				 reject(error);
			 });
		});
	}
}

嗯,很簡單,全程無腦閉着眼睛就寫出來了。可能有人會覺得,哎呀,你很囉嗦哎,又是請求封裝,又是響應封裝,寫個Ajax請求,要這麼繁瑣嗎?嗯,年輕時的我也是這麼想的,其實隨着項目越來越複雜,就是有這個需求,而且,下面會變得越來越複雜。好吧,如果沒興趣,可以出門左轉,不送。

嗯,好吧好吧,我還是再次解釋解釋吧,首先,其實我內心始終是抱着,可以的話,隨時換jQ的Ajax爲別的類庫——當然,前提是那個類庫做的足夠好,足夠完善,別再搞什麼腦殘設計。所以,我不希望在應用層直接使用jQ的方法。

其次,經過多次實戰經驗,我們往往是要對請求的各個細節,比如header,比如默認的queryString,進行一些項目內的微調和項目內的全局定製。當然我可以每個項目都寫一個默認的$.ajax config的模板,事實上的確有項目是這樣實施的,最後還是會發現,有時候爲了製造一個臨時的特殊請求,廢了半天勁,這改那改。所以我們要用面向對象編程,公開的,大大方方將構造XHR請求的每個細節進行透析,讓團隊成員都能看得懂,可以插手進行修正以及調節。

還有,關於Request,其實包含的是一套URI解析合併的類庫,之前我寫了一個ke-url的,但實際使用起來,還是很多問題。當然,這也不是現在的話題了,無論如何,有了這個Request的類,只要確保接口的穩定性,內部實施細節如何調節都無所謂。

接着是調用的示例:

import {JQueryXhrLoader} from './loader/JQueryXhrLoader.js';
// 假定我們放在了 loader的目錄裏

const loader = new JQueryXhrLoader();

loader.load({url: 'http://localhost/a.php'}).then({input, output} => {
    console.log(output.json);
}).catch(err => {
    console.log(err.message);
})

嗯,這調用的代碼,其實可能還不如直接用 $.ajax 的優雅,無所謂,這並不是重點。

標準化的JSON響應

其實我是不推薦直接這樣去使用這個 jQ Loader的,他 output 是XHR底層屬性東西居多,以實際項目而言,往往接口輸出數據,都會有統一的格式,假定我的項目裏,規定了接口必然按照如下接口進行輸出:

{
  "status": true
  "message": "提示消息",
  "data": {}
}

status,必然只會是true or false,message 必然是字符串,data,必然是 object 的key/value結構,來存放接口自定義數據。

我們應當基於項目的實際接口需求,封裝出一個進一步的Response 類,爲了偷懶,我就隨便一拍腦袋決定,Request 構造時,增加一個屬性,statsJSON用於識別是否爲這類型的Response的請求。

接着,基於上述的代碼,我們開始着手改進上面的代碼:

import $ from 'jquery';
import _ from 'lodash';
import Loader from './Loader'; 
// 這裏我把源代碼放到本地,因爲我們的開發環境裏面webpack的編譯環境比較老,babel 6的版本,babel-loader 還將 node_modules 排除了,所以放到本地,進行一些修改。後面 0.0.2版本會着手解決此問題。

import defineProps from 'define-props';

// 請求封裝類
export class JQueryXhrRequest {
	
	id = '';
	
	url = '';
	
	method = 'get';
	
	data = {};
	
	dataType = 'json';
	
	cache = false;
	
	constructor(args) {
		if (typeof args === 'function') args = args();
		if (args instanceof JQueryXhrRequest)
			return args;
		if (_.isString(args)) args = {url: args};
		if (!_.isObjectLike(args)) args = {};
		
		this.url = _.toString(args.url) || '';
		this.method = _.toLower(_.trim(args.method)) || 'get';
		this.data = args.data || {};
		
		this.dataType = _.toLower(args.dataType) || '';
		this.cache = false;

		// 改動#1:增加statusJSON屬性判定
		this.statusJSON = !!args.statusJSON;
		// 改動#1:如果 statusJSON,則dataType必須是json
		if (this.statusJSON) {
			this.dataType = 'json';
		}
	}
	
	getConfig() {
		return {
			url     : this.url,
			method  : this.method,
			date    : this.data,
			cache   : this.cache,
			dataType: this.dataType
		};
	}
}

// 響應的封裝類
export class JQueryXhrResponse {
	
	request = null;
	error = null;
	
	readyState = 0;
	statusCode = 0;
	statusText = '';
	
	text = '';
	json = null;
	
	xhr = null;
	
	constructor(request) {
		this.request = request;
	}
	
	bindResponse(jqXHR, error) {
		this.xhr = jqXHR;
		
		this.readyState = jqXHR.readyState;
		this.statusCode = jqXHR.status;
		this.statusText = jqXHR.statusText;
		this.text = jqXHR.responseText;
		this.json = jqXHR.responseJSON;
		
		return this;
	}
}

// 改動#1:這裏我們增加一個 JQueryXhrStatusJSONResponse 類,不在JQueryXhrResponse 的基礎上去改了,這樣更優雅一點,代碼閱讀起來也更清晰
export class JQueryXhrStatusJSONResponse extends JQueryXhrResponse {
	
    // 改動#1:增加了三個屬性與相應的JSON相對應,並給定一個默認值。
	status = false;
	
	message = '';
	
	data = {};
	
	bindResponse(jqXHR, error) {
        // 繼承原來的方法,並在原來的基礎,將json數據進行拆解,並寫入對應的屬性中。
		super.bindResponse(jqXHR, error);
		if (_.isObjectLike(this.json)) {
			this.status = !!this.json.status;
			this.message = _.toString(this.json.message);
			this.data = _.merge(this.data, this.json.data);
		}
		return this;
	}
}

// Loader 的實現
export class JQueryXhrLoader extends Loader {
	
	newInput(input) {
		return new JQueryXhrRequest(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {JQueryXhrResponse}
	 */
	newOutput(input, output) {
        // 改動#1:增加識別,如果 output 已經是JQueryXhrResponse實例,直接使用該實例
        if (output instanceof JQueryXhrResponse) {
			return output;
		}
        // 改動#1:如果request.statusJSON爲真,則構造一個 JQueryXhrStatusJSONResponse 實例
		if (input.statusJSON) {
			return new JQueryXhrStatusJSONResponse(input);
		}
		return new JQueryXhrResponse(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {Promise<any>}
	 */
	doLoad({input, output}) {
		return new Promise((resolve, reject) => {
			$.ajax(input.getConfig())
			 .done((jqData, textStatus, jqXHR) => {
				 output.bindResponse(jqXHR);
				 resolve();
			 })
			 .fail((jqXHR, textStatus, errorThrown) => {
				 const error = new Error('jQuery ajax load failed!');
				 output.bindResponse(jqXHR, error);
                 // 改動#1:因爲這種數據結構的特殊性,我們希望這類型的請求,不要拋出錯誤,如果服務器出錯,就當默認返回 status = false 的狀態即可。
				 if (input.statusJSON) {
					 resolve();
				 } else {
					 reject(error);
				 }
			 });
		});
	}
}

改動並不大,也不復雜,看起來簡單易懂。

調用上:

import {JQueryXhrLoader} from './loader/JQueryXhrLoader.js';
// 假定我們放在了 loader的目錄裏

const loader = new JQueryXhrLoader();

loader.load({
    url: 'http://localhost/a.php',
    statusJSON: true // 改動#1:聲明這個請求爲 statusJSON 格式
}).then({input, output} => {
    console.log(output.status); // 改動#1:這裏我們輸出這個 status 看看
}).catch(err => {
    console.log(err.message);
})

嗯,雖然我說過,不想修改應用層的調用代碼,但這樣程度的修改,無論誰都是可以接受的。而且事實上真的不想改應用層的代碼,我還有很多辦法可以實施,這裏就不囉嗦了。起碼在調用聲明上,增加一個屬性,使代碼清晰明確的看得出,這是申請 statusJSON的請求,這個調用的相應結果和程序執行,是可預期的。

併發限制

接下來,我們還賊心不死,某種程度上,無論如何我們希望能對併發請求做出一些限制,哪怕是最最簡單的,排隊等待,策略是,我們可以給Ajax進行分組,程序員在實例化Loader的時候自行指定分組名,同組內的所有Loader,都默認遵守該組的併發限制,當一個請求未返回時,後續新增的請求全部掛起不執行,存入隊列,等待第一個請求響應結束,然後再依次加載掛起的請求(也必須one by one的進行,不能呼啦超一下子朝服務器端推送一大票請求出去)。

之前用React.js做純前端時,中後期這個需求成爲一個日益嚴峻的問題,因爲一個數據,往往關聯多個其他相關的碎片數據,純前端化,意味着無法靠服務器端去做數據拼湊,哪怕服務器端只是讀緩存,獲取這些碎片數據,請求量還是會很多,而且當大量用戶同時執行類似操作時,會瞬間造成服務器的壓力,那麼先發起請求的人,可能會迅速得到他們想要的結果,而越靠後的用戶,因爲服務器資源用於應答前面的用戶的請求,導致越後面的用戶,響應速度越慢。嗯,這不管是Java和PHP,還是C#,經過HTTP協議的數據傳輸,就是那麼的不堪一擊。

所以,既然那麼賊心不死,我們就隨便寫個分組玩玩?

import $ from 'jquery';
import _ from 'lodash';
import Loader from './Loader'; 
// 這裏我把源代碼放到本地,因爲我們的開發環境裏面webpack的編譯環境比較老,babel 6的版本,babel-loader 還將 node_modules 排除了,所以放到本地,進行一些修改。後面 0.0.2版本會着手解決此問題。

import defineProps from 'define-props';

// 請求封裝類
export class JQueryXhrRequest {
	
	id = '';
	
	url = '';
	
	method = 'get';
	
	data = {};
	
	dataType = 'json';
	
	cache = false;
	
	constructor(args) {
		if (typeof args === 'function') args = args();
		if (args instanceof JQueryXhrRequest)
			return args;
		if (_.isString(args)) args = {url: args};
		if (!_.isObjectLike(args)) args = {};
		
		this.url = _.toString(args.url) || '';
		this.method = _.toLower(_.trim(args.method)) || 'get';
		this.data = args.data || {};
		
		this.dataType = _.toLower(args.dataType) || '';
		this.cache = false;

		// 改動#1:增加statusJSON屬性判定
		this.statusJSON = !!args.statusJSON;
		// 改動#1:如果 statusJSON,則dataType必須是json
		if (this.statusJSON) {
			this.dataType = 'json';
		}
	}
	
	getConfig() {
		return {
			url     : this.url,
			method  : this.method,
			date    : this.data,
			cache   : this.cache,
			dataType: this.dataType
		};
	}
}

// 響應的封裝類
export class JQueryXhrResponse {
	
	request = null;
	error = null;
	
	readyState = 0;
	statusCode = 0;
	statusText = '';
	
	text = '';
	json = null;
	
	xhr = null;
	
	constructor(request) {
		this.request = request;
	}
	
	bindResponse(jqXHR, error) {
		this.xhr = jqXHR;
		
		this.readyState = jqXHR.readyState;
		this.statusCode = jqXHR.status;
		this.statusText = jqXHR.statusText;
		this.text = jqXHR.responseText;
		this.json = jqXHR.responseJSON;
		
		return this;
	}
}

// 改動#1:這裏我們增加一個 JQueryXhrStatusJSONResponse 類,不在JQueryXhrResponse 的基礎上去改了,這樣更優雅一點,代碼閱讀起來也更清晰
export class JQueryXhrStatusJSONResponse extends JQueryXhrResponse {
	
    // 改動#1:增加了三個屬性與相應的JSON相對應,並給定一個默認值。
	status = false;
	
	message = '';
	
	data = {};
	
	bindResponse(jqXHR, error) {
        // 繼承原來的方法,並在原來的基礎,將json數據進行拆解,並寫入對應的屬性中。
		super.bindResponse(jqXHR, error);
		if (_.isObjectLike(this.json)) {
			this.status = !!this.json.status;
			this.message = _.toString(this.json.message);
			this.data = _.merge(this.data, this.json.data);
		}
		return this;
	}
}

// Loader 的實現
export class JQueryXhrLoader extends Loader {
	
	newInput(input) {
		return new JQueryXhrRequest(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {JQueryXhrResponse}
	 */
	newOutput(input, output) {
        // 改動#1:增加識別,如果 output 已經是JQueryXhrResponse實例,直接使用該實例
        if (output instanceof JQueryXhrResponse) {
			return output;
		}
        // 改動#1:如果request.statusJSON爲真,則構造一個 JQueryXhrStatusJSONResponse 實例
		if (input.statusJSON) {
			return new JQueryXhrStatusJSONResponse(input);
		}
		return new JQueryXhrResponse(input);
	}
	
	/**
	 *
	 * @param {JQueryXhrRequest} input
	 * @param {JQueryXhrResponse} output
	 * @returns {Promise<any>}
	 */
	doLoad({input, output}) {
		return new Promise((resolve, reject) => {
			$.ajax(input.getConfig())
			 .done((jqData, textStatus, jqXHR) => {
				 output.bindResponse(jqXHR);
				 resolve();
			 })
			 .fail((jqXHR, textStatus, errorThrown) => {
				 const error = new Error('jQuery ajax load failed!');
				 output.bindResponse(jqXHR, error);
                 // 改動#1:因爲這種數據結構的特殊性,我們希望這類型的請求,不要拋出錯誤,如果服務器出錯,就當默認返回 status = false 的狀態即可。
				 if (input.statusJSON) {
					 resolve();
				 } else {
					 reject(error);
				 }
			 });
		});
	}
}

// 改動#2:增加一個全局變量,存儲所有的分組實例。
const JQueryXhrGroups = {};

// 改動#2:增加一個分組的類,容我偷懶,這個類還有一些值得優化的空間,但既然我們說了是隨便寫着玩系列,所以就這樣吧
class JQueryXhrGroup {
	
	name = ''; // 分組名
	
	loading = null; // 正在讀取的stream.id
	
	waiting = []; // 掛起中的stream
	
	constructor(name) {
		name = _.trim(name);
		if (typeof JQueryXhrGroups[name] === 'undefined') {
            // 禁止這個實例的name變動
			defineProps(this, {
				name: name
			});
			JQueryXhrGroups[name] = this;
		}
		return JQueryXhrGroups[name];
	}
	
    // 啓動接口,這個實際上是取代Loader的 doLoad 實際執行方法,但我們不選擇在 Loader 裏面動手,寫在外部也是很優雅的嘛
	start(loader, stream) {
		const me = this;
		return new Promise(function (resolve, reject) {
			if (me.loading !== null) {
				me.waiting.push({
					stream : stream,
					promise: new Promise((res, rej) => {
						res({resolve, reject});
					})
				});
			} else {
				me.loading = stream.id;
				resolve(stream);
			}
		});
	}
	
    // 結束一個stream.id,用於清空正在讀取的ID,並且執行 下一個的等待中的任務。
	done(id) {
		if (this.loading === id) {
			this.loading = null;
		}
		this.next();
		return this;
	}
	
    // 執行下一個任務的實際實現,邏輯也很簡單,從waiting隊列裏面將最前面的擠出來,然後找回掛起的Promise ,然後執行他,so easy
	next() {
		if (this.loading === null && this.waiting.length > 0) {
			const item = this.waiting.shift();
			const {stream, promise} = item;
			promise.then(({resolve, reject}) => {
				resolve(stream);
			})
		}
	}
}


// 改動#2:再次,我們再增加一個JQueryGroupedXhrLoader類,我不想改上面已經實現過的類,沒工夫寫那麼多if else
export class JQueryGroupedXhrLoader extends JQueryXhrLoader {
	
    // 改動#2:重載構造函數,默認將分組名作爲第一個參數
	constructor(groupName, args) {
		super(args);
		this.group = new JQueryXhrGroup(groupName);
	}
	
	/**
	 * @param {{input: JQueryXhrRequest, output: JQueryXhrResponse, id: string}} loadStream
	 * @returns {Promise<any>}
	 */
    // 改動#2:重載doLoad的實現,用JQueryXhrGroup.start方法來接管 doLoad 的實現
	doLoad(loadStream) {
        // 改動#2:首先,我們給每個stream都基於分組名生成一個id
		if (typeof loadStream.id === 'undefined') {
			loadStream.id = _.uniqueId('jq_group_' + this.group.name);
		}
		return new Promise((resolve, reject) => {
			return this.group.start(this, loadStream).then(stream => {
                // 調用父類實現的 doLoad 方法
				super.doLoad(stream).then(() => {
					resolve(stream); // 請求成功
				}).catch(err => {
					reject(err); // 請求失敗,還是要照例拋出錯誤的,但因爲在父類已經進行了statusJSON的判定,所以只要請求爲 statusJSON 那麼不會走到這裏來。
				}).finally(() => {
					this.group.done(loadStream.id); // 最終,無論這個 加載是失敗還是成功,我們都要對分組執行一次完成任務,以觸發分組去執行下一個任務。
				});
			})
		})
	}
}

so far so good,僅僅增加了兩個類,按照我們事先設想的,稍微的挪移了一下,大功告成。那麼以下是調用的代碼:

import {JQueryXhrLoader, JQueryGroupedXhrLoader} from './loader/JQueryXhrLoader';

// 指定分組爲Test
const loader = new JQueryGroupedXhrLoader('Test');

// 第一個請求
loader.load({
	url       : 'http://localhost/a.php?temp=3',
	statusJSON: true
}).then(({input, output}) => {
	console.log('a.php', output.data); // 第一個請求會執行到這裏。
    console.log((new Date()).valueOf()); // 輸出一下客戶端到達時間
}).catch(err => {
	// console.log(err.message);
});

// 第二個請求,我們請求一個不存在的地址,並且去掉statusJSON的請求說明
loader.load({
	url       : 'http://localhost/c.php',
	// statusJSON: true
}).then(({input, output}) => {
	console.log('c.php', output.data);
}).catch(err => {
	console.log(err.message); // 第二個請求會執行到這裏。
    console.log((new Date()).valueOf()); // 輸出一下客戶端到達時間
});

// 第三個請求
loader.load({
	url       : 'http://localhost/a.php?temp=1',
	statusJSON: true
}).then(({input, output}) => {
	console.log('a.php', output.data); // 第三個請求會執行到這裏。
    console.log((new Date()).valueOf()); // 輸出一下客戶端到達時間
}).catch(err => {
	console.log(err.message);
});

ok,到此爲止,另外附上a.php的源代碼:

<?php

header('Access-Control-Allow-Origin: *'); // webpack 和 php不是一個host

$data = [
	'status'  => true,
	'message' => 'hello world!',
	'data'    => [
		'date' => date('Y-m-d H:i:s'), // 輸出一下服務器時間
		'ms'   => microtime(true), // 在輸出一下毫秒,方便觀察時間差
	],
];

echo json_encode($data);

ok,上述請求客戶端調試會看到如下截圖:

好,搞完收工。

後記

今天話癆病發作了,就嘮多兩句吧。

之所以選擇jQuery,其實是看中jQuery的人盡皆知。人盡皆知?咿,要知道什麼呢?我有什麼是不知道的?hum....

到目前爲止,any-loader 0.0.1 版本還不算可以直接用於實際使用,還要編譯輸出一下。所以晚上爲了折騰上述這些代碼,倒是折騰了我半天,最後一口氣將前端項目的大環境的babel版本升級到最新版本了(從babel-core 6跳躍到@babel/core  7)。說是折騰,其實我也是密謀很久了,新版babel編譯速度要比舊版快多了,這次好了,徹底升到新版本,整個前端項目裏面都可以直接使用async/await了。

忘記說了,去年某個時間點(大概就是去年現在這個時間點),我忽然心血來潮(也是因爲招了比較新手向的前端工程師),決定將公司所有的前端項目融合在一個大的項目環境下,集中使用一套webpack配置,每個項目有自己的項目入口,有自己的項目版本配置信息,項目的JS和CSS以對應的版本配置的版本號進行文件命名(以做到發生重大異常和錯誤時,可以隨時切換回舊版本),大前端項目也整合CDN同步的指令(npm指令),所以項目只要 release (webpack打包),直接一個指令同步到CDN,以及全局的項目版本聲明文件(不要問我當初爲何想設計這樣一個系統,因爲新人都很氣人也很任性),最後release也push到代碼倉庫,所以就算CDN爆了,隨時再執行一次同步指令,所有版本立刻恢復。

嗯,其實這個系統設計,只是我所有野心的一個起點,我的野心當然是徹底的用React.js做純前端網站響應式兼容應用,但這需要很多組件儲備。所以這一年,隨着市場和需求的發展,這個項目裏的React組件也越來越豐富。而且因爲在一個統一的編譯環境下,今天我爲A項目寫的組件,其他項目即可立刻獲益,真是非常的爽快(這一年下來,我也是佩服自己當初的心血來潮)。組件即可隨項目一起合併打包,也可以獨立帶版本號輸出獨立的js和樣式文件,以作爲插件庫使用。時至今日,所積累的組件庫也算是基本滿足行業內的需求了。

從某個時間點開始(大概2015年下半年吧),做前端,不管你是專精CSS方向,還是JS方向,已經註定走上了一條不歸路。註定了,必須以折騰爲本命,而且不管用任何開發語言,都沒有Web前端那麼折騰,因爲npm庫的發展繁殖速度實在太快了,版本號迭代的速度已經到了令人髮指的地步——這不,纔將React 16.6.1整合到基礎庫,全部組件測試一遍,人家又出16.6.3了。前端之折騰,簡直是要人命的,從當初的gulp、webpack、babel初代,乃至到今天的@babel,從最初的安哥拉JS(到今天我都鄙視這貨),到今天的Vuejs和Reactjs雙駕馬車並駕齊驅。現在的技術潮流,尤其是前端領域,已經逼着你必須時刻懷抱着,隨時乾死自己,徹底推翻掉昨天的你,熬一個通宵折騰,將會是一個全新的你。

來吧,少年,爲了那個全新的自我,開始折騰吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章