【问题标题】:Javascript - Delaying module return/declaration in Require.js?Javascript - 在 Require.js 中延迟模块返回/声明?
【发布时间】:2013-09-15 23:23:08
【问题描述】:

我有一个模块,它返回一个由 JSON 数据和图像对象组成的数组。由于加载 JSON(从其他文件)和图像对象都需要时间,因此我需要我的模块仅在两者都完成后才返回数组。

目前,该模块总是在其他模块中返回“未定义”,我相信这是因为该模块没有像我期望的那样等待返回(但我不确定)。或者,因为使用此 Atlas 模块的其他模块在返回任何内容之前将其声明为变量。

已编辑以显示我如何定义/需要模块 *再次编辑以显示更多代码*

The live code can be seen here.

这是我的 tile-atlas 模块:

define( function() {

var tilesheetPaths = [
            "tilesheets/ground.json",
    "tilesheets/ground-collision.json",
    "tilesheets/objects-collision.json"
];

var tileAtlas = [ ];


function loadAtlasJSON() {  
    for (var i = 0; i < tilesheetPaths.length; i++) {
        loadJSON( 
            {
                fileName: tilesheetPaths[ i ],
                success: function( atlas ) {            
                    addToTileAtlas( atlas );
                }
            } 
        );
    }
};


function addToTileAtlas( atlas ) {
    atlas.loaded = false;
    var img = new Image();

    img.onload = function() {
        atlas.loaded = true;
    };

    img.src = atlas.src;

    // Store the image object as an "object" property
    atlas.object = img;
    tileAtlas[ atlas.id ] = atlas;
}


// Returns tileAtlas[ ] once everything is loaded and ready
function tileAtlasReady() {
    if ( allJSONloaded() && allImagesLoaded() ) {
        console.log("TileAtlas ready"); 
        return tileAtlas;
    }
    console.log("TileAtlas not ready");
    setTimeout(tileAtlasReady, 10);
};


// Checks to make sure all XMLHttpRequests are finished and response is added to tileAtlas
function allJSONloaded() {
    // If the tilesheet count in tileAtlas !== the total amount of tilesheets
    if ( Object.size(tileAtlas) !== tilesheetPaths.length ) {
        // All tilesheets have not been loaded into tileAtlas
        console.log("JSON still loading");
        return false;
    } 
    console.log("All JSON loaded");
    return true;
};


// Checks that all img objects have been loaded for the tilesheets
function allImagesLoaded() {
    for ( var tilesheet in tileAtlas ) {
        if (tileAtlas[tilesheet].loaded !== true) {
            console.log("Images still loading");
            return false;
        }
    }
    console.log("All images loaded");
    return true;
};


// Loads the JSON/images
loadAtlasJSON();

// Module should only return when tileAtlasReady() returns
return tileAtlasReady();

} );

这是我的库中的 loadJSON 函数:

window.loadJSON = function( args ) {
    var xhr = new XMLHttpRequest();
    xhr.overrideMimeType( "application/json" );
    xhr.open( "GET", args.fileName, true );

    xhr.onreadystatechange = function () {
        if ( xhr.readyState == 4 ) {        

            if ( xhr.status == "200" ) {
                // Check that response is valid JSON
                try {
                    JSON.parse( xhr.responseText );
                } catch ( e ) {
                    console.log( args.fileName + ": " + e );
                    return false;
                }
                args.success( JSON.parse(xhr.responseText) );
            // xhr.status === "404", file not found
            } else {
                console.log("File: " + args.fileName + " was not found!");
            }
        }
    }
    xhr.send();
}   

以及加载我的 tile-atlas 模块的模块:

define( ['data/tile-atlas'], function( tileAtlas ) {

function displayImages() {
    // Code
};

// Returns undefined
console.log(tileAtlas);

displayKey();

} );

这是输出的样子:

[12:14:45.407] "JSON still loading"
[12:14:45.407] "TileAtlas not ready"
[12:14:45.408] undefined 

[12:14:45.428] "JSON still loading"
[12:14:45.428] "TileAtlas not ready"

[12:14:45.469] "All JSON loaded"
[12:14:45.470] "Images still loading"
[12:14:45.470] "TileAtlas not ready"

[12:14:45.481] "All JSON loaded"
[12:14:45.481] "Images still loading"
[12:14:45.481] "TileAtlas not ready"

[12:14:45.492] "All JSON loaded"
[12:14:45.492] "All images loaded"
[12:14:45.492] "TileAtlas ready"

“未定义”来自当我从另一个模块控制台记录我的 Atlas 模块时,这取决于 Atlas 模块。

我不确定是 Atlas 模块提前返回了某些东西,还是其他模块在返回之前将 Atlas 模块声明为变量。

但如果是后者,有没有办法让模块在它们的依赖项完成返回某些东西之前不会运行?

我对 Require.js 和 AMD 完全陌生:这种方法是否存在固有缺陷?我认为将 AMD 与加载敏感模块一起使用很常见。

感谢您的帮助。

编辑查看另一个游戏的源代码,我意识到我可以使用 require.js 文本插件加载我的 JSON 文件,而不是实现 XHR在我的模块中。这要简单得多,因为我不需要处理在模块中等待 XHR 的复杂性。 Here is how BrowserQuest does so.

【问题讨论】:

  • 您能否展示完整的模块(即包括define 部分),以及它的使用位置示例(即requireing 模块的示例)?
  • 您好,我添加了定义部分,但省略了我认为与加载情况无关的代码。如果您也希望我添加省略的代码,请告诉我。谢谢。
  • loadAtlasJSON 是如何实现的?这一点至关重要,因为只有当 this 返回并且解析结果时,您才能安全地从模块中返回任何内容。
  • 嗨,我添加了其余的详细信息 + 一个指向实时代码的链接。我的 loadAtlasJSON 没有返回任何东西。它只是加载 JSON 并放入 tileAtlas[]。谢谢你的关注;非常感谢:)

标签: javascript json requirejs amd


【解决方案1】:

好的,我检查了您的代码并有一些观察结果。

  • 除非您确切地知道自己在做什么(或者这样做是为了了解 XHR),否则我不会直接使用 XMLHttpRequest。这个对象有几个跨浏览器的问题,所以我会使用 JS 库来帮助解决问题,jQuery 是最明显的问题之一。

  • 我也会尝试使用延迟/承诺方法而不是回调。同样,图书馆将在这里帮助您。 jQuery has these for ajax,就像when.js 等其他库一样。

因此,考虑到这一点,这里是您可能想查看的some jsfiddle code,它可能会给您一些想法。

首先,新模块中有一个 ajax 抽象,它使用 when.js 延迟。该模块将根据 XHR 是否成功来解决或拒绝延迟。注意我已经离开了直接使用 XHR,但我建议改用 JS 库。

// --------------------------------------------------
// New module
// Using https://github.com/cujojs/when/wiki/Examples
// --------------------------------------------------

define("my-ajax", ["when"], function (when) {
    // TODO - Fake id only for testing
    var id = 0;

    function makeFakeJSFiddleRequestData(fileName) {
        var data = {
            fileName: fileName
        };
        data = "json=" + encodeURI(JSON.stringify(data));
        var delayInSeconds = Math.floor(8 * Math.random());
        data += "&delay=" + delayInSeconds;
        return data;
    }

    return function loadJSON(args) {
        // Create the deferred response
        var deferred = when.defer();

        var xhr = new XMLHttpRequest();
        xhr.overrideMimeType("application/json");

        var url = args.fileName;

        // TODO - Override URL only for testing
        url = "/echo/json/";

        // TODO - Provide request data and timings only for testing
        var start = +new Date();
        var data = makeFakeJSFiddleRequestData(args.fileName);

        // TODO - POST for testing. jsfiddle expects POST.
        xhr.open("POST", url, true);

        xhr.onreadystatechange = function () {
            // TODO - duration only for testing.
            var duration = +new Date() - start + "ms";
            if (xhr.readyState == 4) {
                if (xhr.status === 200) {
                    // Check that response is valid JSON
                    var json;
                    try {
                        json = JSON.parse(xhr.responseText);
                    } catch (e) {
                        console.log("rejected", args, duration);
                        deferred.reject(e);
                        return;
                    }
                    console.log("resolved", args, duration);
                    // TODO - Fake id only for testing
                    json.id = ("id" + id++);
                    deferred.resolve(json);
                } else {
                    console.log("rejected", args, duration);
                    deferred.reject([xhr.status, args.fileName]);
                }
            }
        }

        // TODO - Provide request data only for testing
        xhr.send(data);

        // return the deferred's promise.
        // This promise will only be resolved or rejected when the XHR is complete.
        return deferred.promise;
    };
});

现在您的 atlas 模块看起来有点像这样(为清楚起见,删除了图像代码):

// --------------------------------------------------
// Your module
// Image stuff removed for clarity.
// --------------------------------------------------

define("tile-atlas", ["my-ajax", "when"], function (myAjax, when) {

    var tilesheetPaths = [
        "tilesheets/ground.json",
        "tilesheets/ground-collision.json",
        "tilesheets/objects-collision.json"];

    // TODO - Changed to {} for Object keys
    var tileAtlas = {};

    function loadAtlasJSON() {
        var deferreds = [];
        // Save up all the AJAX calls as deferreds
        for (var i = 0; i < tilesheetPaths.length; i++) {
            deferreds.push(myAjax({
                fileName: tilesheetPaths[i]
            }));
        }
        // Return a new deferred that only resolves
        // when all the ajax requests have come back.
        return when.all(deferreds);
    };

    function addToTileAtlas(atlas) {
        console.log("addToTileAtlas", atlas);
        tileAtlas[atlas.id] = atlas;
    }

    function tileAtlasesReady() {
        console.log("tileAtlasesReady", arguments);
        var ajaxResponses = arguments[0];
        for (var i = 0; i < ajaxResponses.length; i++) {
            addToTileAtlas(ajaxResponses[i]);
        }
        return tileAtlas;
    };

    function loadAtlases() {
        // When loadAtlasJSON has completed, call tileAtlasesReady.
        // This also has the effect of resolving the value that tileAtlasesReady returns.
        return when(loadAtlasJSON(), tileAtlasesReady);
    }

    // Return an object containing a function that can load the atlases
    return {
        loadAtlases: loadAtlases
    };
});

您的应用(或我的假演示)可以通过以下方式使用此代码:

// --------------------------------------------------
// App code
// --------------------------------------------------

require(["tile-atlas"], function (atlas) {
    console.log(atlas);

    // The then() callback will only fire when loadAtlases is complete
    atlas.loadAtlases().then(function (atlases) {
        console.log("atlases loaded");
        for (var id in atlases) {
            console.log("atlas " + id, atlases[id]);
        }
    });
});

并将以下类型的信息转储到控制台:

Object {loadAtlases: function}
resolved Object {fileName: "tilesheets/ground-collision.json"} 3186ms
resolved Object {fileName: "tilesheets/ground.json"} 5159ms
resolved Object {fileName: "tilesheets/objects-collision.json"} 6221ms
tileAtlasesReady     [Array[3]]
addToTileAtlas Object {fileName: "tilesheets/ground.json", id: "id1"}
addToTileAtlas Object {fileName: "tilesheets/ground-collision.json", id: "id0"}
addToTileAtlas Object {fileName: "tilesheets/objects-collision.json", id: "id2"}
atlases loaded
atlas id1 Object {fileName: "tilesheets/ground.json", id: "id1"}
atlas id0 Object {fileName: "tilesheets/ground-collision.json", id: "id0"}
atlas id2 Object {fileName: "tilesheets/objects-collision.json", id: "id2"}

【讨论】:

  • 哇,感谢您详细而广泛的回复!首先,关于 XMLHttpRequest,我试图避免使用库,除非我真的知道发生了什么并且可以重新创建它们。不幸的是,作为 JS 的新手,这排除了很多可能性。我想了解 XHR。有没有
  • 有没有可以推荐的文章详细介绍 XHR 的“陷阱”?关于您的新模块,似乎 when.js 库正是我所需要的。我现在会花一些时间来学习它,但从描述性的名称来看,它似乎很直观。但是我想知道,通过带有异步模块的 XHR 加载文件是一种很常见的情况吗?这种场景真的是“ajax”吗?使用这种延迟/承诺方法是最常见的方法吗?
  • 最后,在修改后的 tile-atlas 模块中,您返回一个包含 loadAtlases 属性的对象。既然你这样做了,这是否意味着没有办法真正延迟主模块在准备好时返回实际的 tileAtlas 对象?另外,有没有办法延迟其他模块声明 tile-atlas 变量的值?我是出于好奇而询问,并了解异步模块的限制。非常感谢您的时间和精力。我希望有一个“给你买啤酒”按钮 :)
  • 查看stackoverflow.com/questions/2557247/…github.com/ilinsky/xmlhttprequest/blob/master/XMLHttpRequest.js的跨浏览器XHR,但是如果你搜索它们还有很多其他文章。
  • 通过带有异步模块的 XHR 加载文件是一种很常见的情况吗? - 是的。我经常合理地这样做这种场景真的是“ajax”吗? - 在维基百科上阅读 AJAX 是更好的解释,其他可搜索的 AJAX 文章也是如此使用这种延迟/承诺方法是最常见的方法吗? - 延迟/承诺正在成为处理异步编程复杂性的推荐方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-02
  • 2017-12-28
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
  • 2012-05-28
相关资源
最近更新 更多