当异步函数不返回 Promise 时,为什么我需要等待它?

编程入门 行业动态 更新时间:2024-10-22 20:21:11
本文介绍了当异步函数不返回 Promise 时,为什么我需要等待它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

考虑这个代码:

异步函数加载(){const 数据 = 等待新的承诺(解决 => {setTimeout(() => resolve([1, 2, 3]), 10);}).then(data => data.map(i => i * 10));console.log(`函数内的数据:${JSON.stringify(data)}`);返回数据;}函数主(){常量数据 = 加载();console.log(`加载的数据:${JSON.stringify(data)}`);}主要的();

这是我得到的输出:

加载的数据:{}函数内部数据:[10,20,30]

但是如果我把代码改成这样:

异步函数加载(){const 数据 = 等待新的承诺(解决 => {setTimeout(() => resolve([1, 2, 3]), 10);}).then(data => data.map(i => i * 10));console.log(`函数内的数据:${JSON.stringify(data)}`);返回数据;}异步 函数 main() {const data = await load();console.log(`加载的数据:${JSON.stringify(data)}`);}主要的();

我会得到这个:

函数内部数据:[10,20,30]加载数据:[10,20,30]

我很困惑,因为基于 文档, await 应该暂停执行,直到承诺得到解决.在这种情况下,第一个示例应该将 data 作为数组返回.但是正如你所看到的,它返回一个 Promise 而我不知道为什么!?

同时,文档中有这部分我不明白它在说什么:

await 可以拆分执行流,允许await 的调用者在延迟继续之前恢复执行的函数等待的功能.在 await 推迟其继续之后函数,如果这是函数执行的第一个等待,立即执行也继续返回到函数的调用者为完成等待函数的挂起 Promise并恢复执行该调用者.

在我看来,await 仅在您代码中的所有 函数都是 async 时才起作用,这很荒谬,因为如果我'm 使用来自另一个模块的函数,我怎么知道它是否是 async !?或者,也许我应该采取谨慎的态度,始终使用 await 调用所有函数,无论它们是否为 async !!!

[更新]

感谢所有参与并为我提供洞察力的人.但是我仍然很困惑我应该如何使用 await 和 async.我应该总是用 await 调用我的所有函数吗?

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

解决方案

所有 async 函数都返回一个 promise.他们都是.

这个承诺最终会用你从异步函数返回的任何值来解决.

await 仅阻止 async 函数内部的执行.它不会阻塞函数之外的任何东西.从概念上讲,异步函数开始执行,一旦遇到 await 指令,它会立即从函数返回一个未实现的承诺,外部执行世界得到该承诺并继续执行.

稍后,被awaited 的内部promise 将解析,然后函数内部其余部分的执行将继续.最终函数的内部将完成并返回一个值.这将触发解析从具有该返回值的函数返回的承诺.

仅供参考,您的 load() 函数中有很多多余的东西.您可以从这里更改它:

异步函数加载(){const 数据 = 等待新的承诺(解决 => {setTimeout(() => resolve([1, 2, 3]), 10);}).then(data => data.map(i => i * 10));console.log(`函数内的数据:${JSON.stringify(data)}`);返回数据;}

为此:

function load() {返回新的承诺(解决 => {setTimeout(() => resolve([1, 2, 3]), 10);}).then(data => data.map(i => i * 10));}

然后,像这样使用它:

load().then(result => {控制台日志(结果);});

或者,我更喜欢像这样将promise的手动创建封装在自己的函数中:

函数延迟(t, v) {返回新的承诺(解决 => {setTimeout(resolve.bind(null, v), t);});}函数加载(){return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));}

而且,事实证明,这个小 delay() 函数通常在许多需要延迟承诺链的地方很有用.

感谢所有参与并为我提供洞察力的人.但我仍然很困惑我应该如何使用 await 和 async.

首先,大多数情况下,如果您需要在函数内部使用 await,您只会标记函数 async.

其次,当您有多个异步操作并且想要对它们进行排序时,您最常使用 await(在 async 函数中) - 通常是因为第一个提供用作第二个输入的结果.当您只有一个异步操作时,您可以使用 await,但与简单的 .then() 相比,它并没有真正提供太多优势.

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

对多个异步操作进行排序

假设您有 getFromDatabase()、getTheUrl() 和 getTheContent(),它们都是异步的.如果有任何失败,您只想拒绝返回的第一个错误的承诺.

这是没有 async/await 时的样子:

function run() {返回 getFromDatabase(someArg).then(key => {返回 getTheURL(key);}).then(url => {返回 getTheContent(url);}).那么(内容=>{//一些最后的处理返回最终值;});}

这是使用 async/await 的样子:

异步函数运行(someArg){let key = await getFromDatabase(someArg);让 url = 等待 getTheURL(key);让内容 = 等待 getTheContent(url);//一些最后的处理返回最终值;}

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

run(someArg).then(finalValue => {控制台日志(最终值);}).catch(err => {控制台日志(错误);});

但是,您会注意到 async/await 实现具有更多的序列化、同步外观,并且看起来更像是非异步代码.许多人发现这更易于编写、阅读和维护.步骤之间的处理越多,包括分支,async/await 版本的优势就越大.

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

正如我之前所说,async 函数总是返回一个 promise.他们还必须内置错误处理,自动将错误传播回返回的承诺.

不言而喻,如果您从 async 函数手动返回一个承诺并且该承诺被拒绝,那么从 async 函数返回的承诺将被拒绝.>

而且,如果您使用 await 并且您正在等待的任何承诺被拒绝,并且您在承诺上没有 .catch() 并且没有在它周围有一个 try/catch,那么函数返回的承诺将自动拒绝.所以,回到我们之前的例子:

异步函数运行(someArg){let key = await getFromDatabase(someArg);让 url = 等待 getTheURL(key);让内容 = 等待 getTheContent(url);//一些最后的处理返回最终值;}

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

最后,async 函数还会为您捕获同步异常并将它们转换为被拒绝的承诺.

在一个像我们之前那样返回承诺的普通函数中:

function run() {返回 getFromDatabase(someArg).then(key => {返回 getTheURL(key);}).then(url => {返回 getTheContent(url);}).那么(内容=>{//一些最后的处理返回最终值;});}

如果getFromDatabase()抛出一个同步异常(可能是因为someArg无效而触发),那么这个整体函数run()会抛出同步.这意味着调用者要从 run() 捕获所有可能的错误,他们必须用 try/catch 将它包围起来以捕获同步异常并使用 .catch() 捕获被拒绝的承诺:

尝试{运行(someArg).then(finalValue => {控制台日志(最终值);}).catch(err => {控制台日志(错误);});}赶上(e){控制台日志(错误);}

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

异步函数运行(someArg){let key = await getFromDatabase(someArg);让 url = 等待 getTheURL(key);让内容 = 等待 getTheContent(url);//一些最后的处理返回最终值;}//将从 run() 中捕获所有可能的错误运行(someArg).then(finalValue => {控制台日志(最终值);}).catch(err => {控制台日志(错误);});

我应该总是用 await 调用我的所有函数吗?

首先,您只能将 await 与返回承诺的函数一起使用,因为 await 如果该函数不返回承诺(只是添加到如果不需要,您的代码会变得混乱).

其次,是否使用 await 取决于调用函数的上下文(因为您必须在 async 函数中才能使用 await 以及逻辑流程以及它是否从使用 await 中受益.

在没有意义的地方使用 await

异步函数 getKey(someArg) {let key = await getFromDatabase(someArg);返回键;}

这里的 await 没有做任何有用的事情.您没有对多个异步操作进行排序,也没有对返回值进行任何处理.您可以通过直接返回承诺来完成完全相同的代码:

异步函数 getKey(someArg) {返回 getFromDatabase(someArg);}

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

function getKey(someArg) {返回 getFromDatabase(someArg);}

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

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

  • 一旦您尝试从函数 A() 返回的结果的任何部分是异步的或使用任何异步操作来获取,则该函数本身就是异步的.在纯 Javascript 中,您永远无法同步返回异步结果,因此您的函数必须使用异步方法来返回结果(promise、回调、事件等...).

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

  • 没有特别需要标记函数 async 除非您特别需要 async 函数的好处之一,例如使用 的能力await 在该函数内或它提供的自动错误处理.您可以编写返回 Promise 的函数,而无需在函数声明中使用 async.所以,不"我不会返回调用链使一切async.如果有特定的原因,我只会使函数异步.通常这个原因是我想在函数内部使用 await,但也有同步异常的自动捕获,这些异常会变成我之前描述的承诺拒绝.对于行为良好的代码,您通常不需要它,但有时对于行为不佳的代码或具有未定义行为的代码很有用.

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

  • 或者也许我应该养成使用 await 调用所有函数的习惯,而不管它们是否异步?

    绝对不是!首先,您要做的最后一件事是采用完全同步的代码并不必要地使其异步,甚至使其看起来是异步的.异步代码(即使使用 async 和 await)编写、调试、理解和维护比同步代码更复杂,因此您永远不想不必要地将同步代码变成异步代码通过在其中添加 async/await :

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

    异步函数 random(min, max) {让 r = 等待 Math.random();返回 Math.floor((r * (max - min)) + min);}

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

    function random(min, max) {让 r = Math.random();返回 Math.floor((r * (max - min)) + min);}

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

    random(1,10).then(r => {控制台.log(r);});

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

    console.log(random(1,10));

    Consider this code:

    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();

    This is the output I'm getting:

    Loaded data: {} Data inside the function: [10,20,30]

    But if I change the code to this:

    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; } async function main() { const data = await load(); console.log(`Loaded data: ${JSON.stringify(data)}`); } main();

    I'll get this:

    Data inside the function: [10,20,30] Loaded data: [10,20,30]

    I'm confused because based on the documentation, await should pause the execution till the promise is resolved. In which case, the first example should return data as an array. But as you can see, it's returning a Promise and I have no idea why!?

    At the same time, the documentation has this part which I don't understand what it is talking about:

    An await can split execution flow, allowing the caller of the await's function to resume execution before the deferred continuation of the await's function. After the await defers the continuation of its function, if this is the first await executed by the function, immediate execution also continues by returning to the function's caller a pending Promise for the completion of the await's function and resuming execution of that caller.

    It seems to me the await is working only if all of the functions in your code are async which is ridiculous since if I'm using a function from another module, how should I know if it's an async or not!? Or maybe I should take the cautious side and always call all the functions with an await regardless of whether they are async or not!!!

    [UPDATE]

    Thanks to everyone participating and providing me with insight. But I'm still confused how should I be using await and async. Should I always call all of my function with an await?

    Let's say I'm writing a code composed of multiple functions within multiple files. If I end up using a library which returns a Promise or it's an async function, should I trace back all my function calls from the asynchronous point to the entry point of the application and add an await before all the function calls after making them async? Or maybe I should just get into the habit of calling all my functions with an await regardless of whether they are async or not?

    解决方案

    All async functions return a promise. All of them.

    That promise will eventually resolve with whatever value you return from the async function.

    await only blocks execution internal to the async function. It does not block anything outside of the function. Conceptually, an async function starts to execute and as soon as it hits an await instruction, it immediately returns an unfulfilled promise from the function and the outside execution world gets that promise and continues to execute.

    Sometime later, the internal promise that was being awaited will resolve and then the execution of the rest of the internals of the function will continue. Eventually the internals of the function will finish and return a value. That will trigger resolving the promise that was returned from the function with that return value.

    FYI, there's a lot of superfluous stuff in your load() function. You can change it from this:

    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; }

    to this:

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

    Then, use it like this:

    load().then(result => { console.log(result); });

    Or, I prefer to encapsulate the manual creation of promise in their own function like this:

    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)); }

    And, it turns out this little delay() function is generally useful in lots of places where you want to delay a promise chain.

    Thanks to everyone participating and providing me with insight. But I'm still confused how should I be using await and async.

    First off, most of the time you only mark a function async if you need to use await inside the function.

    Second, you most commonly use await (from within an async function) when you have multiple asynchronous operations and you want to sequence them - often because the first one provides a result that is used as input to the second. You can use await when all you have is a single asynchronous operation, but it doesn't really offer much of an advantage over a simple .then().

    Here are a few examples of good reasons to use async/await:

    Sequencing multiple asynchronous operations

    Imagine you have getFromDatabase(), getTheUrl() and getTheContent() that are all asynchronous. If any fails, you would want to just reject the returned promise with the first error.

    Here's how this looks without async/await:

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

    Here's how this looks with 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; }

    In both cases, the function returns a promise that resolves with the finalValue so these two implementations are used the same by the caller:

    run(someArg).then(finalValue => { console.log(finalValue); }).catch(err => { console.log(err); });

    But, you will notice that the async/await implementation has more of a serialized, synchronous look to it and looks more like non-asynchronous code. Many find this easier to write, easier to read and easier to maintain. The more processing you have between steps, including branching, the more advantages accrue to the async/await version.

    Automatically catching both rejected promises and synchronous exceptions

    As I said earlier, async functions always return a promise. They also have to built-in error handling that automatically propagates errors back to that returned promise.

    It goes without saying that if you manually return a promise from the async function and that promise rejects, then the promise returned from the async function will reject.

    But also, if you are using await and any promise you are awaiting rejects and you don't have a .catch() on the promise and don't have a try/catch around it, then the promise the function returns will automatically reject. So, back in our previous example of this:

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

    If any of the three promises that are being awaited reject, then the function will short circuit (stop executing any more code in the function) and reject the async returned promise. So, you get this form of error handling for free.

    Then lastly, an async function also catches synchronous exceptions for you and turns them into a rejected promise.

    In a normal function that returns a promise such as we had earlier:

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

    If getFromDatabase() throws a synchronous exception (perhaps triggered because someArg is invalid), then this overall function run() will throw synchronously. That means that for the caller to catch all possible errors from run(), they have to both surround it with a try/catch to catch the synchronous exceptions and use a .catch() to catch the rejected promise:

    try { run(someArg).then(finalValue => { console.log(finalValue); }).catch(err => { console.log(err); }); } catch(e) { console.log(err); }

    This is messy and a bit repetitive. But, when run() is declared async, then it will NEVER throw synchronously because any synchronous exception is automatically converted to a rejected promise so you can be sure you are capturing all possible errors when it's written this way:

    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); });

    Should I always call all of my function with an await?

    First, you would only ever use await with a function that returns a promise as await offers no usefulness if the function does not return a promise (just adding to the clutter of your code if not needed).

    Second, whether you use await or not depends upon the context of both the calling function (since you HAVE to be in an async function to use await and on the flow of logic and whether it benefits from using await or not.

    Places where it's pointless to use await

    async function getKey(someArg) { let key = await getFromDatabase(someArg); return key; }

    The await here isn't doing anything useful. You're not sequencing multiple async operations and you're not doing any processing on the return value. You can accomplish the exact same code by just returning the promise directly:

    async function getKey(someArg) { return getFromDatabase(someArg); }

    And, if you know that getFromDatabase() never throws synchronously, you can even remove the async from the declaration:

    function getKey(someArg) { return getFromDatabase(someArg); }

    Let's say I'm writing a code composed of multiple functions within multiple files. If I end up using a library which returns a Promise or it's an async function, should I trace back all my function calls from the asynchronous point to the entry point of the application and add an await before all the function calls after making them async?

    This is a bit too general of an ask that's hard to answer in a general case. Here are some thoughts along this general direction:

  • Once any part of your result that you're trying to return from your function A() is asynchronous or uses any asynchronous operation to obtain, the function itself is asynchronous. In plain Javascript, you can never return an asynchronous result synchronously so your function must use an asynchronous method to return the result (promise, callback, event, etc...).

  • Any function B() that calls your asynchronous function A() that is also trying to return a result based on what it gets from A() is now also asynchronous and also must communicate its result back using an asynchronous mechanism. This is true for a function C() that calls B() and needs to communicate back its result to the caller. So, you can say that asynchronous behavior is infectious. Until you reach some point in the call chain where you no longer need to communicate back a result, everything has to use asynchronous mechanisms to communicate the result, error and completion.

  • There's no specific need to mark a function async unless you specifically need one of the benefits of an async function such as the ability to use await inside that function or the automatic error handling it provides. You can write functions that returning promises just fine without using async on the function declaration. So, "NO" I don't go back up the call chain making everything async. I only make a function async if there's a specific reason to do so. Usually that reason is that I want to use await inside the function, but there is also the automatic catching of synchronous exceptions that get turned into promise rejections that I described earlier. You would not generally need that with well behaved code, but it is sometimes useful with poorly behaved code orcode with an undefined behavior.

  • await is also only used when there's a specific reason for it. I don't just automatically use it on every function that returns a promise. I've described above reasons to use it. One can still use .then() just fine for processing the result from a single function call that returns a promise. In some cases, it's just a matter of personal style whether you want to use .then() or await and there is no particular reason it has to be one one way or the other.

  • Or maybe I should just get into the habit of calling all my functions with an await regardless of whether they are async or not?

    Absolutely NOT! First off, the last thing you want to do is to take perfectly synchronous code and unnecessarily make it asynchronous or even make it look asynchronous. Asynchronous code (even with async and await) is more complicated to write, debug, understand and maintain than synchronous code so you would never want to unnecessarily make synchronous code into asynchronous code by adding async/await into it:

    For example, you would never do this:

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

    First off, this is a perfectly synchronous operation that can be coded like this:

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

    Second off, that first async implementation has made the function a lot hard to use as it now has an asynchronous result:

    random(1,10).then(r => { console.log(r); });

    Instead of just the simple synchronous use:

    console.log(random(1,10));

    更多推荐

    当异步函数不返回 Promise 时,为什么我需要等待它?

    本文发布于:2023-11-05 07:55:18,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1560290.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:函数   Promise

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!