为什么应该在不返回Promise的情况下等待异步函数?

Meh*_*ran 5 javascript node.js promise async-await ecmascript-6

考虑以下代码:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

function main() {
  const data = load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出:

Loaded data: {}
Data inside the function: [10,20,30]
Run Code Online (Sandbox Code Playgroud)

但是,如果我将代码更改为此:

异步功能load(){
  const data =等待新的Promise(resolve => {
    setTimeout(()=> resolve([1,2,3]),10);
  })。then(data => data.map(i => i * 10));
  console.log(`函数内部的数据:$ {JSON.stringify(data)}`));
  返回数据;
}

异步函数main(){
  const data = 等待 load();
  console.log(`已加载数据:$ {JSON.stringify(data)}`);
}

主要();

我会得到这个:

Data inside the function: [10,20,30]
Loaded data: [10,20,30]
Run Code Online (Sandbox Code Playgroud)

我很困惑,因为基于文档,await应该暂停执行直到承诺被解决。在这种情况下,第一个示例应data以数组形式返回。但是正如您所看到的,它返回a Promise,我不知道为什么!

同时,文档包含这一部分,我不明白它在说什么:

等待可以拆分执行流程,从而允许等待函数的调用者在推迟执行等待函数之前恢复执行。在等待推迟了其功能的继续之后,如果这是该功能执行的第一个等待,则立即执行也将继续,方法是将未完成的Promise返回给函数的调用方,以完成等待功能并恢复该调用方的执行。

在我看来,await仅当代码中的所有函数都是async荒谬的时,该函数才起作用,因为如果我使用其他模块中的函数,我怎么知道它是否是一个async!或者,也许我应该保持谨慎的态度,并且await无论是否调用所有函数,都应始终调用它们async!!!

[更新]

感谢大家的参与,并为我提供了见识。但是我仍然困惑我应该如何使用await和async。我是否应该始终使用调用所有函数await?

假设我正在编写一个由多个文件中的多个函数组成的代码。如果我最终使用的是一个返回a Promise或async函数的库,是否应该追溯我从异步点到应用程序入口点的await所有函数调用,并在所有函数调用之后添加一个async?或者,也许我应该养成await无论是否调用我的所有函数的习惯async?

jfr*_*d00 14

所有async函数都返回一个承诺。他们都是。

该承诺最终将使用您从异步函数返回的任何值来解决。

await仅阻止async函数内部的执行。它不会阻止该功能以外的任何内容。从概念上讲,异步函数开始执行,并且一旦它遇到一条await指令,它就会立即从该函数返回一个未兑现的承诺,并且外部执行世界会获得该承诺并继续执行。

稍后的某个时间,正在await编辑的内部承诺将解决,然后将继续执行该函数的其余内部部分。最终,函数的内部将完成并返回一个值。这将触发解析使用该返回值从函数返回的promise。

仅供参考,您的load()功能中有很多多余的东西。您可以从中更改它:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}
Run Code Online (Sandbox Code Playgroud)

对此:

function load() {
    return new Promise(resolve => {
        setTimeout(() => resolve([1, 2, 3]), 10);
    }).then(data => data.map(i => i * 10));
}
Run Code Online (Sandbox Code Playgroud)

然后,像这样使用它:

load().then(result => {
    console.log(result);
});
Run Code Online (Sandbox Code Playgroud)

或者,我更喜欢将promise的手动创建封装在自己的函数中,如下所示:

function delay(t, v) {
    return new Promise(resolve => {
        setTimeout(resolve.bind(null, v), t);
    });
}

function load() {
    return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}
Run Code Online (Sandbox Code Playgroud)

而且,事实证明,这个小delay()功能通常在许多您希望延迟承诺链的地方很有用。


感谢大家的参与,并为我提供了见识。但是我仍然很困惑如何使用等待和异步。

首先,大多数情况下,仅async在需要await在函数内部使用时才标记函数。

其次,当您有多个异步操作并且要对其进行排序时,最常使用await(从async函数内部)-通常是因为第一个操作提供的结果用作第二个操作的输入。await当您只有一个异步操作时就可以使用,但与simple相比,它实际上并没有提供太多优势.then()。

以下是一些使用理由充分的示例async/await:

排序多个异步操作

假设您有getFromDatabase(),getTheUrl()而getTheContent()这些都是异步的。如果有任何失败,您只想拒绝返回的第一个错误的承诺。

没有异步/等待的情况如下:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}
Run Code Online (Sandbox Code Playgroud)

这是这样的async/await:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,该函数都返回一个使用finalValue解析的promise,因此调用者使用这两种实现是相同的:

run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});
Run Code Online (Sandbox Code Playgroud)

但是,您会注意到该async/await实现具有更多的串行化,同步外观,并且看起来更像是非异步代码。许多人发现这更容易编写,易于阅读并且易于维护。您在步骤之间(包括分支)进行的处理越多,该async/await版本将获得更多的优势。

自动捕获拒绝的承诺和同步异常

如前所述,async函数总是返回一个承诺。他们还必须具有内置的错误处理功能,该功能可以将错误自动传播回返回的承诺。

不用说,如果您从async函数手动返回一个承诺并且该承诺被拒绝,那么从async函数返回的承诺将被拒绝。

但是,如果您正在使用await并且正在等待拒绝的任何promise,并且没有.catch()promise且没有try/catchpromise,则函数返回的promise将自动被拒绝。因此,回到我们之前的示例中:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}
Run Code Online (Sandbox Code Playgroud)

如果awaited 的三个承诺中的任何一个被拒绝,则该函数将短路(停止执行该函数中的更多代码)并拒绝异步返回的承诺。因此,您可以免费获得这种形式的错误处理。

最后,async函数还可以为您捕获同步异常并将其转变为被拒绝的承诺。

在一个正常的函数中,它返回一个像我们之前那样的承诺:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
         // some final processing
         return finalValue;
    });
}
Run Code Online (Sandbox Code Playgroud)

如果getFromDatabase()引发同步异常(可能是因为someArg无效而触发),则此总体功能run()将同步引发。这意味着,对于调用方来说,要从中捕获所有可能的错误run(),他们都必须在其周围加上a try/catch来捕获同步异常,并使用a .catch()来捕获被拒绝的promise:

try {
    run(someArg).then(finalValue => {
        console.log(finalValue);
    }).catch(err => {
        console.log(err);
    });
} catch(e) {
    console.log(err);
}
Run Code Online (Sandbox Code Playgroud)

这很麻烦,而且有点重复。但是,当run()声明了时async,它将永远不会同步抛出,因为任何同步异常都会自动转换为拒绝的诺言,因此您可以确定以这种方式编写时捕获了所有可能的错误:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;        
}

// will catch all possible errors from run()
run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});
Run Code Online (Sandbox Code Playgroud)

我是否应该始终等待我调用所有函数?

首先,如果函数不返回承诺,则只会await与返回承诺的函数一起使用(await如果不需要的话,只会增加代码的混乱程度)。

其次,是否使用await取决于调用函数的上下文(因为您async必须使用要使用的函数await以及逻辑流程,以及逻辑是否可以从使用中受益)await。

毫无用处的地方

async function getKey(someArg) {
    let key = await getFromDatabase(someArg);
    return key;
}
Run Code Online (Sandbox Code Playgroud)

在await这里没有做什么有用的东西。您没有对多个异步操作进行排序,也没有对返回值进行任何处理。您可以通过直接返回promise来完成完全相同的代码:

async function getKey(someArg) {
    return getFromDatabase(someArg);
}
Run Code Online (Sandbox Code Playgroud)

而且,如果您知道getFromDatabase()从不同步引发,则甚至可以async从声明中删除:

function getKey(someArg) {
    return getFromDatabase(someArg);
}
Run Code Online (Sandbox Code Playgroud)

假设我正在编写一个由多个文件中的多个函数组成的代码。如果我最终使用返回Promise的库或它是一个异步函数,那么我应该追溯从异​​步点到应用程序入口点的所有函数调用,并在使所有函数调用异步之后在所有函数调用之前添加await吗?

这个问题有点笼统,在一般情况下很难回答。以下是沿着这个大致方向的一些想法:

  1. 尝试从函数返回的结果的任何部分A()都是异步的或使用任何异步操作来获取时,函数本身就是异步的。在普通的Javascript中,您永远不能同步返回异步结果,因此您的函数必须使用异步方法返回结果(承诺,回调,事件等)。

  2. 现在,任何B()调用异步函数A()并试图根据其返回结果返回结果的函数也都是A()异步的,并且还必须使用异步机制将其结果传递回去。对于C()调用B()并需要将结果返回给调用方的函数而言,这是正确的。因此,可以说异步行为具有传染性。在到达调用链中不再需要传递回结果的某个位置之前,一切都必须使用异步机制来传递结果,错误和完成。

  3. async除非您特别需要功能的优点之一,async例如可以await在该功能中使用功能或它提供的自动错误处理功能,否则无需特别标记功能。您可以编写返回promise的函数,而无需使用async函数声明。因此,“不”,我不回头做所有事情的电话链async。仅在有特定原因时才使函数异步。通常原因是我要使用await在函数内部,但也有自动捕获同步异常的功能,这些异常已变成我之前介绍的promise拒绝。通常,对于行为良好的代码,通常并不需要这样做,但是对于行为不良的代码或行为未定义的代码,有时这很有用。

  4. await也仅在有特定原因时使用。我不仅会在返回promise的每个函数上自动使用它。我已经描述了使用它的原因。仍然可以使用.then()一个函数来处理返回一个Promise的单个函数调用的结果。在某些情况下,您是否要使用.then()还是仅是个人风格的问题,await并且没有特殊原因必须采用一种或另一种方式。

或者,也许我应该养成在等待时调用所有函数的习惯,无论它们是否异步?

绝对不!首先,您要做的最后一件事是获取完全同步的代码,并使其不必要地异步,甚至使其看起来异步。异步代码(甚至带有async和await)与同步代码相比,编写,调试,理解和维护也更为复杂,因此您永远不需要通过添加异步代码将异步代码不必要地变成异步代码async/await:

例如,您永远不会这样做:

async function random(min, max) {
    let r = await Math.random();
    return Math.floor((r * (max - min)) + min);
}
Run Code Online (Sandbox Code Playgroud)

首先,这是一个完全同步的操作,可以这样编码:

function random(min, max) {
    let r = Math.random();
    return Math.floor((r * (max - min)) + min);
}
Run Code Online (Sandbox Code Playgroud)

其次,第一个async实现使该函数难以使用,因为它现在具有异步结果:

 random(1,10).then(r => {
     console.log(r);
 });
Run Code Online (Sandbox Code Playgroud)

不仅仅是简单的同步使用:

 console.log(random(1,10));
Run Code Online (Sandbox Code Playgroud)

  • @Paulpro但是你仍然不能在javascript中做到这一点 - 你需要在C中做到这一点(你可以在node.js中做到这一点,是的,但使用节点的C API而不是节点的javascript API) (2认同)

afe*_*ter 6

async/await只是语法糖,这意味着它们不会给语言带来任何新功能,只是对承诺有用的包装。

如果一个函数被标记为async,它总是返回一个承诺:

> async function f() { return 42; }
undefined
> f()
Promise { 42 }
Run Code Online (Sandbox Code Playgroud)

此外,如果一个函数是async,您可以await对其内部的任何 Promise(包括另一个函数的结果async)进行操作,并且函数代码的执行将暂停,await直到该 Promise 得到解决或拒绝。

回答你的问题:如果你使用库函数,你通常知道它是否返回一个承诺(如果它被标记为async,它肯定会返回)。因此,请确保await使用它或.then与返回的承诺一起使用。

  • 我完全同意“await”和“async”使您的代码更具可读性。我想说的是,他们并不是他们应该成为的样子。在我看来,它们的实施是错误的。 (2认同)