我正在开发一款游戏,代码中有一个位置可以并行加载所有资源。目前它看起来像这样:

let [
    grassTexture,
    stoneTexture,
    waterTexture,
    birdSound,
    bellSound,
    choochooSound
] = await Promise.all([
    loadGrassTexture(),
    loadStoneTexture(),
    loadWaterTexture(),
    loadBirdSound(),
    loadBellSound(),
    loadChoochooSound()
])

但我觉得这不是特别好的设计,因为很容易弄乱顺序(加载器和加载器之间的关联),如果结果与相应的函数调用在同一行会更好。我看到的另一种选择是:

let grassTexturePromise = loadGrassTexture()
let stoneTexturePromise = loadStoneTexture()
let waterTexturePromise = loadWaterTexture()
let birdSoundPromise = loadBirdSound()
let bellSoundPromise = loadBellSound()
let choochooSoundPromise = loadChoochooSound()

let grassTexture = await grassTexturePromise
let stoneTexture = await stoneTexturePromise
let waterTexture = await waterTexturePromise
let birdSound = await birdSoundPromise
let bellSound = await bellSoundPromise
let choochooSound = await choochooSoundPromise

但这也没什么好处。有没有什么惯用的方法可以做到这一点?

编辑:

我很抱歉这是一个不断变化的目标,我在这篇文章中简化了太多内容。在我的代码中,我使用的是 TypeScript,并且资源有不同的类型。我还根据评论更正了第二个列表。

7

  • 1
    为什么不将函数引用也放入数组中?


    – 

  • @Pointy 你是什么意思?它们在一个数组中,不是吗?


    – 

  • 1
    我看到的另一种选择是: ”是的,不要那样做。1. 它并没有真正实现你想要的效果(*Texture变量仍然是承诺)2.


    – 

  • 您可以使用Promise.allSettled,输出比来自的输出稍微复杂一些Promise.all,但您会收到哪些被拒绝以及哪些被满足的信息(顺序也被保留)


    – 

  • 1
    @Yogi 我认为他只是意味着与函数相比,解构模式中的变量很容易以错误的顺序获取,因此变量会获得错误的值。


    – 


最佳答案
4

不要对每个资源使用单独的变量,而是将它们放在以资源名称为键的对象中。

然后您可以使用Promise.all()循环来填充值。

const textures = {
    grass: loadGrassTexture(),
    stone: loadStoneTexture(),
    // ...
};

const results = await Promise.all(Object.values(textures));
Object.keys(textures).forEach((key, i) => textures[key] = results[i]);

2

  • 谢谢!这对我来说看起来最优雅。但是,我认为这不适用于 TypeScript。很抱歉,我忘了说我正在使用 TypeScript…


    – 

  • 我不使用 TypeScript,但就我对它的了解,我不明白为什么它不起作用。


    – 

您可以将其重构为参数化函数:


//if you can refactor you code to a parameterized load texture function 

async function loadTexture(textureName) {
  //your new code here
}

const textureNames = ['grass', 'stone', 'water', 'sky', 'sand', 'wood', 'carpet', 'paper', 'steel'];

const texturePromises = textureNames.map(async (name) => [name, await loadTexture(name)]);
const textureEntries = await Promise.all(texturePromises);
//now we have a mapping of texture type -> texture
const textureObject = Object.fromEntries(textureEntries);

我建议创建一个单独的命名空间文件。

例如:

loaders.mjs

// PLACE NECESSARY IMPORTS HERE

// The functions below should return Promises.

export const grassTexture  = loadGrassTexture();
export const stoneTexture  = loadStoneTexture();
export const waterTexture  = loadWaterTexture();
export const skyTexture    = loadSkyTexture();
export const sandTexture   = loadSandTexture();
export const woodTexture   = loadWoodTexture();
export const carpetTexture = loadCarpetTexture();
export const paperTexture  = loadPaperTexture();
export const steelTexture  = loadSteelTexture();

// The order below doesn't matter...
export default Promise.all
([
   grassTexture, stoneTexture, waterTexture,
   skyTexture, sandTexture, woodTexture,
   carpetTexture, paperTexture, steelTesture,
]);

然后在任何其他文件中,您可以像这样加载它们:

// Import the loaders at the script root.
import texturePromises, * as loaders from './loaders.mjs';

// Then use at an asynchronous thread...
async function installTextures ()
{
   // Stops the thread until all Promises are fulfilled.
   await texturePromises;
   
   // From here on everything is loaded...
   
   // Note that the order doesn't matter.
   const { paperTexture, skyTexture } = loaders;
   
   // Use as needed.
}

// Or import just the needed part for a specific component directly...
import { stoneTexture, grassTexture, waterTexture } from './loaders.mjs';

请注意,该await关键字将停止线程,直到相应的 Promise 得到满足。确保仅在真正需要时使用。在大多数情况下,最好依赖该.then()方法。

如果你不是特别喜欢原生 JavaScript,你可以考虑使用

let {
    grassTexture,
    stoneTexture,
    ...
} = await Bluebird.props({
    grassTexture: loadGrassTexture(),
    stoneTexture: loadStoneTexture(),
    ...
})