搜索

首页  >  问答  >  正文

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

比如我要做一个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里。

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

大家讲道理大家讲道理2790 天前1090

全部回复(5)我来回复

  • 伊谢尔伦

    伊谢尔伦2017-04-10 14:53:51

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

    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 也许会有收获。

    回复
    0
  • 阿神

    阿神2017-04-10 14:53:51

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

    回复
    0
  • PHPz

    PHPz2017-04-10 14:53:51

    我也介绍一个吧
    https://github.com/caolan/async

    回复
    0
  • 迷茫

    迷茫2017-04-10 14:53:51

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

    回复
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-10 14:53:51

    q也不错 习惯angular的$q

    回复
    0
  • 取消回复