Wind.js在移动跨平台框架PhoneGap中的异步体验

最近正在做一个移动跨平台项目的应用开发,包括在iphone,ipad,android,windows phone等手机设备中运行混合式客户端应用程序,这里选择了PhoneGap的移动跨平台框架,这里我先简单介绍下PhoneGap到底是什么东东:

 

介绍

PhoneGap是一款HTML5平台,通过它,开发商可以使用HTML、CSS及JavaScript来开发本地移动应用程序。因此,目前开发商可以只 编写一次应用程序,然后在6个主要的移动平台和应用程序商店(app store)里进行发布,这些移动平台和应用程序商店包括:iOS、Android、BlackBerry、webOS、bada以及Symbian。

官方地址

英文官方:http://phonegap.com/

中文官方:http://www.phonegap.cn/

在上面你可以找到它的入门使用说明,这里我就不具体描述了。

关于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来体验下如何应用到我的项目中去。

请先看下面的一段代码:

function login(userName, password, type, callback) {
    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)来接收返回的数据。

最后的执行:

$(document).delegate("#loginPage #loginButton", "click", function () {
    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的函数:

//phonegap异步调用插件
$.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与原生客户端的交互。

最后的执行: 

$(document).delegate("#loginPage #loginButton", "click", function () {
    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");
        }
    }
}));

理解上就相当简单了。

实例图:

4d555398gw1dw0v5lyas1j (1).jpg

总结

会持续关注Wind.js的发展,并且接下来也会了解下它的内部原理。另外,提出一个建议,eval(Wind.compile('async',…这样的写法还能够更加简易些吗,比如我觉得使用$.await(function(…){ … });就是挺好的方式。

开源地址:https://github.com/sunleepy/cooper-mobi

posted @ 2012-09-02 10:32 Leepy 阅读(...) 评论(...) 编辑 收藏