requirejs模块化设计中对于异步回调函数,怎么处理较好?

mc_zone
  • 282

比如我要做一个H5页面,有一些动画效果,同时带音频播放
目前我有一个app模块为主要逻辑。有一个sound模块用来初始化音频。

我是使用SoundManager 2这个音频库来初始我的音频,需要的使用方法如下。

//sound.js
define(["SoundManager"],function(sm2){
    var mySound;

    sm2.soundManager.setup({
        url:'js/sm2/swf/',
        onready: function() {
            mySound = sm2.soundManager.createSound({ 
                url: 'sound/sound1.mp3',
                autoLoad: true
            });
        }
    });

    return {
        mySound:mySound
    };
});

但是实际上 onready方法是在音频开始加载后才异步执行的。这使得我在app中获取到的mySound是undefined。

//app.js
define(["sound"], function(sound) {
        var mySound = sound.mySound;// undefined
});

于是我改成在return的时候这样写

//sound.js修改后的return
return{
    getMySound:function(){
        return mySound;
    }
};

然后在app中使用的时候每次都是如下:

//app.js
define(["sound"], function(sound) {
    var mySound = sound.getMySound();
    if(typeof mySound !== 'undefined'){
        //doSometing()...
    }
});

我的模块化设计思路是不是有问题?在这种回调下怎么设计要好一些?

而且如果我要在mySound加载成功后再执行一些任务,就只能到sound.js中去做了。但是我又想把类似的任务放到app.js里。

如果在普通模式下。可能我是习惯了不断的回调深渊了,现在接触模块化设计有点不够灵光。

回复
阅读 7k
5 个回答
东来
  • 1.7k
✓ 已被采纳

题主所遇到的问题,可以进行拆分。

requirejs 帮你解决的是模块间依赖问题,和js代码异步加载问题。

但是 requirejs 并没有帮你解决异步流程控制问题

而题主所遇到的就是典型的 异步流程控制问题

使用高阶函数

这种问题有很多解决方案,其中最简单的的方法就是 高阶函数 (js 是一个强大的函数式编程语言)

javascript// 楼主的 资源模块 代码可以这样定义
//sound.js
define(["SoundManager"],function(sm2){
    return function(done) {
        sm2.soundManager.setup({
            url:'js/sm2/swf/',
            onready: function() {
                // 返回 sound
                done(sm2.soundManager.createSound({ 
                    url: 'sound/sound1.mp3',
                    autoLoad: true
                }));
            }
        });
    }
});
javascript// 楼主的 资源调用模块 代码可以这样定义
//app.js
define(["sound"], function($sound) {
    $sound(function(sound){
        // sound ready
        // sound.play()
    });
});

利用 高阶函数 可以犀利的解决这个问题,但是高阶函数就像 c 语言的指针。强大但是很容易让你头疼。

可以使用社区中 async 模块,可以帮你轻松管理 异步流程。它完全使用 js 高阶函数实现。

前端安装模块: bower install async

使用 Q (Promise/Deferred)

还有一种 解决方案 就是 Q,Q 包含了 Promise 实现,和 Deferred 实现。

使用 deferred 可以比较优雅的处理这个问题

javascript// 楼主的 资源模块 代码可以这样定义
// 这里使用 Chrome version >= 32.0 支持 Promise
//sound.js
define(["SoundManager"],function(sm2){
    var promise = new Promise(function(resolve, reject){
        sm2.soundManager.setup({
            url:'js/sm2/swf/',
            onready: function() {
                // 返回 sound
                try {
                    var sound = sm2.soundManager.createSound({ 
                        url: 'sound/sound1.mp3',
                        autoLoad: true
                    });
                    // success return
                    resolve(sound);
                } catch (ex) {
                    // error return
                    reject(new Error(ex))
                }
            }
        }); 
    });
    // 返回 promise 对象
    return promise;
});
javascript// 楼主的 资源调用模块 代码可以这样定义
//app.js
define(["sound"], function(sound) {
    sound.then(
        // success handler
        function(sound){
        // sound ready
        // sound.play()
        },
        // error handler
        function(err){
            console.error(err);
        }
    );
});

Q 可以优雅的封装我们的异步代码,管理异步中的异常,著名 jQuery 类库 1.5 之后ajax部分使用 Promise 重写,流行的前端框架 AngularJS 中的 $q 就是一个 Promise 的实现,这种模式在 java中也有所使用。

Chrome 第32个版本中内置了 Promise 类,来支持这个功能。 但是 IE 目前没有支持。

并不推荐使用浏览器自带的 Promise

在前端,如果项目中,自带有 jquery 类库的话。

javascript// $.Deferred

//sound.js
define(["SoundManager", "jQuery"],function(sm2, $){
        var deferred = $.Deferred();
        sm2.soundManager.setup({
            url:'js/sm2/swf/',
            onready: function() {
                // 返回 sound
                try {
                    var sound = sm2.soundManager.createSound({
                        url: 'sound/sound1.mp3',
                        autoLoad: true
                    });
                    // success return
                    deferred.resolve(sound);
                } catch (ex) {
                    // error return
                    deferred.reject(new Error(ex))
                }
            }
        }); 
    });
    // 返回 promise 对象
    return deferred;
});

如果项目中没有jQuery的话,可以使用 q 模块, 还有类似的 when 模块。

bower install q 只有 2.5 KB

了解更多 Promise, 传送门 链接描述

使用 generator (仅供尝鲜)

** chrome >= 39 支持, 在 nodejs > 0.11.x 支持**

javascript// 楼主的 资源模块 代码可以这样定义
//sound.js
define(["SoundManager"],function(sm2){
    return function(callback){
        sm2.soundManager.setup({
            url:'js/sm2/swf/',
            onready: function() {
                // 返回 sound
                try {
                    var sound = sm2.soundManager.createSound({ 
                        url: 'sound/sound1.mp3',
                        autoLoad: true
                    });
                    // success return
                    callback(null, sound);
                } catch (ex) {
                    // error return
                    callback(new Error(ex));
                }
            }
        });
    };
});

是的 还要利用 高阶函数

配置好 requirejs

json    paths: {
        'co': '../vendor/co/co',
        'setimmediate': '../vendor/setimmediate/setimmediate'
    },

    shim: {
        co: {
            deps: ['setimmediate'],
            exports: 'co'
        }
    }

这里需要一个 叫 co 模块。
bower install co co 在 浏览器中依赖 setImmediate 模块,setImmediate 在 nodejs 中是原生

javascript// 楼主的 资源调用模块 代码可以这样定义
//app.js
define(["co", "sound"], function(co, $sound) {
    co(function *(){
        function $sound_thunkify() { return $sound;}

        var sound = yield $sound_thunkify();

        // sound ready
        // sound.play()

    });
});

function $sound_thunkify() { return $sound;} 这段为什么会这样。

tj 称这个过程叫 trunkify 翻译过来叫做 块状化

你可以使用一个叫做 trunkify 的 模块

bower install trunkify

javascript// 楼主的 资源调用模块 代码可以这样定义
//app.js
define(["co", "trunkify", "sound"], function(co, trunkify) {
    co(function *(){

        var sound = yield trunkify($sound);

        // sound ready
        // sound.play()

    });
});

Generator 的出现,并不是取代 Promise 模式,他们是互补的。

如果对这种方式感兴趣的话,可以看看 朴灵的文章 -> Generator 也许会有收获。

Promise技术可以帮到你,将模块内容定为promise对象即可回避null/undefine(想拿拿不到)和加载成功后不知道(拿到了又触发不下去)的问题

使用thenjs也可以这样的,而且比promise更加直观。

q也不错 习惯angular的$q

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏