最近正在做一個移動跨平台項目的應用開發,包括在iphone,ipad,android,windows phone等手機設備中運行混合式客戶端應用程序,這里選擇了PhoneGap的移動跨平台框架,這里我先簡單介紹下PhoneGap到底是什么東東:
介紹
PhoneGap是一款HTML5平台,通過它,開發商可以使用HTML、CSS及JavaScript來開發本地移動應用程序。因此,目前開發商可以只 編寫一次應用程序,然后在6個主要的移動平台和應用程序商店(app store)里進行發布,這些移動平台和應用程序商店包括:iOS、Android、BlackBerry、webOS、bada以及Symbian。
官方地址
英文官方:http://phonegap.com/
在上面你可以找到它的入門使用說明,這里我就不具體描述了。
關於PhoneGap開發的項目實踐經驗,我會在后面再另開文章來說明。
本篇文章的重點在於對於老趙的Wind.js的使用體驗,老趙是誰,相信也不用多說了,具體可以見他的博客:http://blog.zhaojie.me/
這里就先介紹他的Wind.js:
官方鏈接:http://windjs.org/
開源鏈接:https://github.com/JeffreyZhao/wind
對於它的定義,我就直接引用官方的一段話:
Wind.js的前身為Jscex,即JavaScript Computation EXpressions的縮寫,它為JavaScript語言提供了一個monadic擴展,能夠顯著提高一些常見場景下的編程體驗(例如異步編程)。Wind.js完全使用JavaScript編寫,能夠在任意支持JavaScript的執行引擎里使用,包括各瀏覽器及服務器端JavaScript環境(例如Node.js)。
實際上,它就是原先的Jscex。
對於我來說,正在開始了解Wind.js的時候,是在今年的7月份,在阿里技術嘉年華(http://adc.taobao.com/)中聽node.js專場時了解到的,當時現場火爆,大家對於Wind.js也是非常感興趣。當然過程中也有一些人保持一種觀望的態度。而對於我這種實戰派的開發者來說,在一個有應用場景的情況下,Wind.js才更有說服力。
於是,在我當時的理解當中,它應該是屬於可以改變異步體驗的一種絕佳的方式,可以讓你的代碼可讀性大大提升,而你從此不再為了setTimeout,callback之類的寫法而煩惱!
好的,一些理論的東西我就先不多說了,Wind.js文檔中已經寫的很多了,今天就來用Wind.js來體驗下如何應用到我的項目中去。
請先看下面的一段代碼:
if (callback == null) {
return;
}
callIfNetworkAvailable( function() {
callNativeAPI(
native_refreshUrl,
{ key: 'Login', username: userName, password: password, type: type },
function (result) {
callback(result);
}
);
});
}
function callIfNetworkAvailable(fn) {
if (fn == null) {
return;
}
getNetworkStatus( function (result) {
if (result.status) {
if (result.data) {
fn();
}
else {
hideLoadingMsg();
alert(lang.networkUnAvailable);
}
}
else {
hideLoadingMsg();
alert(lang.getNetworkAvailableStatusFailed);
}
});
}
function getNetworkStatus(callback) {
if (callback == null) {
return;
}
callNativeAPI(
native_getUrl,
{ key: 'GetNetworkStatus' },
function (result) {
callback(result);
}
);
}
function callNativeAPI(url, data, callback) {
var items = url.split("/");
var serviceName = items[0];
var actionName = items[1].toLowerCase();
// 因為參數必須是數組,所以把參數放在一個數組中
var params = [];
params.push(data);
log({ step: '調用Native接口前的參數信息', parameters: data });
// 調用Native接口
Cordova.exec(
function (result) {
log({ step: '調用Native接口的返回值信息', returnValue: result });
if (callback != null) {
callback(result);
}
},
function () { },
serviceName,
actionName,
params
);
}
這里首先我先需要說的是,PhoneGap的javascript腳本與原生(iOS,android,wp等)的API的plugin交互,采用與瀏覽器webkit中的webview進行通信,而它的底層原理就是iframe的交互,它是以一種特定規范的通信協議來展開,而在傳統的web上iframe的使用本身就是最原始的異步加載原理的使用。所以,沒有辦法異步方式在phonegap的開發中廣泛使用。
再回過頭看上面的代碼,它實際上實現的是一個登錄的功能,在登錄的過程中,首先我必須先調用callIfNetworkAvailable方法,而它的參數本身作為一個回調函數,getNetworkStatus會調用callNativeAPI,callNativeAPI函數里面的Cordova.exec方法實際上就是一個跟原生交互的一個方法入口,通過傳遞serviceName,actionName以及對應需要傳遞的數據參數來決定,而它總是需要一個回調方法(successCallback,failCallback)來接收返回的數據。
最后的執行:
var userName = $("#username").val();
var password = $("#password").val();
if (isNullOrEmpty(userName)) {
alert(lang.usernameCannotEmpty);
return;
}
if (isNullOrEmpty(password)) {
alert(lang.passwordCannotEmpty);
return;
}
showLoading("登錄中...");
login(userName, password, "normal", function (result) {
if (!result.status) {
alert(result.message);
}
else {
showPage("taskListPage");
hideLoading();
}
});
});
我們得到的就是需要不斷地寫嵌套函數,不斷地callback,這樣的寫法看起來還是很糾結的!
於是,萌生了采用Wind.js的異步調用的獨特方式:
先來看看我是如何改造這段代碼的:
var loginAsync = eval(Wind.compile('async', function (userName, password, type) {
var result = $await(callIfNetworkAvailableAsync());
if(result) {
var result = $await(callNativeAPIAsync(native_refreshUrl,
{ key: 'Login', username: userName, password: password, type: type }));
return result;
}
return null;
}));
// 在當前網絡可用的情況下調用指定函數
var callIfNetworkAvailableAsync = eval(Wind.compile('async', function() {
var result = $await(getNetworkStatusAsync());
if (result.status) {
if (result.data) {
return true;
}
else {
hideLoadingMsg();
alert(lang.networkUnAvailable);
}
}
else {
hideLoadingMsg();
alert(lang.getNetworkAvailableStatusFailed);
}
return false;
}));
// 獲取當前網絡狀態
var getNetworkStatusAsync = eval(Wind.compile('async', function() {
var result = $await(callNativeAPIAsync(native_getUrl, { key: 'GetNetworkStatus' }));
return result;
}));
// JS端與PhoneGap Native API進行交互
var callNativeAPIAsync = eval(Wind.compile('async', function(url, data) {
var items = url.split("/");
var serviceName = items[0];
var actionName = items[1].toLowerCase();
// 因為參數必須是數組,所以把參數放在一個數組中
var params = [];
params.push(data);
$await(logAsync({ step: '調用Native接口前的參數信息', parameters: data }));
// 調用Native接口
var result = $await($.cordovaAsync(serviceName, actionName, params));
if(result) {
$await(logAsync({ step: '調用Native接口的返回值信息', returnValue: result }));
}
return result;
}))
從代碼中,你不難發現,它采用一種順序執行代替異步的方式很巧妙地繞開了一連串callback的寫法,在感官上似乎更符合了一個開發者順序執行模型的思想。
而return result;就是一個回調的結果。
到這里你可能還有個疑問,Cordova.exec是如何做到的,這里我定義了一個叫做$.cordovaAsync的函數:
$.cordovaAsync = function (serviceName, actionName, params) {
return Task.create( function (t) {
var success = function (result) {
t.complete("success", result);
};
var fail = function(result) {
t.complete("failure", result);
}
Cordova.exec(success, fail, serviceName, actionName, params);
});
}
這里是Wind.js提供的一種任務式的插件綁定,例如它可以把一個jQuery中的$.ajax改裝成一個$.ajaxAsync的Wind.js調用方式,這里我把它改裝成$.cordovaAsync來調用Cordova.exec,而最終var result = $await($.cordovaAsync(serviceName, actionName, params));的返回值實際上就是一個回調函數的success,這樣我就實現了JS與原生客戶端的交互。
最后的執行:
var userName = $("#username").val();
var password = $("#password").val();
if (isNullOrEmpty(userName)) {
alert(lang.usernameCannotEmpty);
return;
}
if (isNullOrEmpty(password)) {
alert(lang.passwordCannotEmpty);
return;
}
showLoading("登錄中...");
startLoginAsync(userName, password, 'normal').start();
});
var startLoginAsync = eval(Wind.compile('async', function (userName, password, type) {
var result = $await(loginAsync(userName, password, type));
if(result) {
if (!result.status) {
alert(result.message);
}
else {
hideLoading();
showPage("taskListPage");
}
}
}));
理解上就相當簡單了。
實例圖:
總結
會持續關注Wind.js的發展,並且接下來也會了解下它的內部原理。另外,提出一個建議,eval(Wind.compile('async',…這樣的寫法還能夠更加簡易些嗎,比如我覺得使用$.await(function(…){ … });就是挺好的方式。