javascript 异步函数没有返回值,但 console.log() 有:怎么办?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/47664598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 07:32:24  来源:igfitidea点击:

Async function not returning value, but console.log() does: how to do?

javascriptecmascript-6async-await

提问by Jona Rodrigues

I have an es6 class, with an init()method responsible for fetching data, transforming it, then update the class's property this.datawith newly transformed data. So far so good. The class itself has another getPostById()method, to just do what it sounds like. Here is the code for the class:

我有一个 es6 类,有一个init()方法负责获取数据,转换它,然后this.data用新转换的数据更新类的属性。到现在为止还挺好。类本身有另一种getPostById()方法,只是做它听起来像的事情。这是该类的代码:

class Posts {
  constructor(url) {
    this.ready = false
    this.data = {}
    this.url = url
  }
  async init() {
      try { 
        let res = await fetch( this.url )
        if (res.ok) {
            let data = await res.json()

          // Do bunch of transformation stuff here

          this.data = data
          this.ready = true
            return data
        }
      } 
      catch (e) { 
         console.log(e)
      }
  }
  getPostById(id){
     return this.data.find( p => p.id === id )
  }
}  

Straightforward, except I have an async/awaitmechanism in the init()method. Now, this code will work correctly:

直截了当,除了我async/awaitinit()方法中有一个机制。现在,此代码将正常工作:

let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')

allPosts.init()
        .then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console

but it only gets printed into the console: How could I use allPosts.getPostById(4)as a returnof a function ?

但它只会打印到控制台中:我如何将其allPosts.getPostById(4)用作return函数?

Like:

喜欢:

let myFunc = async () => {
   const postId = 4
   await allPosts.init()  // I need to wait for this to finish before returning

   // This is logging correct value
   console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )

   // How can I return the RESULT of allPosts.getPostById( postId ) ???
   return allPosts.getPostById( postId )
}

myFunc()returns a Promisebut not the final value. I have read several related posts on the subject but they all give example of logging, never returning.

myFunc()返回一个Promise但不是最终值。我已经阅读了几篇关于这个主题的相关文章,但它们都给出了日志记录的例子,再也没有回来。

Here is a fiddlethat includes two ways of handling init(): using Promiseand using async/await. No matter what I try, I can't manage to USE the FINAL VALUE of getPostById(id).

这是一个包含两种处理方式的小提琴init(): usingPromise和 using async/await。无论我尝试什么,我都无法使用getPostById(id).

The question of this post is: how can I create a function that will RETURN the VALUE of getPostById(id)?

这篇文章的问题是:如何创建一个函数来返回 的值getPostById(id)

EDIT:

编辑:

A lot of good answers trying to explain what Promises are in regards to the main execution loop. After a lot of videos and other good reads, here is what I understand now:

很多很好的答案试图解释关于主执行循环的 Promises 是什么。经过大量的视频和其他好的阅读,以下是我现在的理解:

my function init()correctly returns. However, within the main event loop: it returns a Promise, then it is my job to catch the result of this Promise from within a kindaparallel loop (not a new real thread). In order to catch the result from the parallel loop there are two ways:

我的函数init()正确返回。然而,在主事件循环中:它返回一个 Promise,然后我的工作是从一个有点并行的循环(不是一个新的真正线程)中捕获这个 Promise 的结果。为了从并行循环中捕获结果,有两种方法:

  1. use .then( value => doSomethingWithMy(value) )

  2. use let value = await myAsyncFn(). Now here is the foolish hiccup:

  1. 利用 .then( value => doSomethingWithMy(value) )

  2. 使用let value = await myAsyncFn(). 现在这是愚蠢的打嗝:

await can only be used within an asyncfunction :p

await 只能在async函数中使用:p

thus itself returning a Promise, usable with awaitwhich should be embed in an asyncfunction, which will be usable with awaitetc...

因此它本身返回一个 Promise,可以使用await它应该嵌入到一个async函数中,它可以与await等等一起使用......

This means we cannot really WAIT for a Promise: instead we should catch parallel loop indefinitely: using .then()or async/await.

这意味着我们不能真正等待 Promise:相反,我们应该无限期地捕获并行循环:使用.then()or async/await

Thanks for the help !

谢谢您的帮助 !

回答by HMR

As for your comment; I'll add it as answer.

至于你的评论;我将其添加为答案。

The code you write in JavaScript is run on one thread, that means that if your code could actually wait for something it will block any of your other code from getting executed. The event loop of JavaScript is explained very well in this videoand if you like to read in this page.

您用 JavaScript 编写的代码在一个线程上运行,这意味着如果您的代码实际上可以等待某些东西,它将阻止您执行任何其他代码。JavaScript 的事件循环在这个视频中得到了很好的解释,如果你喜欢阅读这个页面

A good example of blocking code in the browser is alert("cannot do anything until you click ok");. Alert blocks everything, the user can't even scroll or click on anything in the page and your code also blocks from executing.

在浏览器中阻止代码的一个很好的例子是alert("cannot do anything until you click ok");. 警报会阻止所有内容,用户甚至无法滚动或单击页面中的任何内容,您的代码也会阻止执行。

Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
  x=>console.log(
    "does not resolve untill you click ok on the alert:",
    x
  )
);

Run that in a console and you see what I mean by blocking.

在控制台中运行它,你就会明白我所说的阻塞是什么意思。

This creates a problem when you want to do something that takes time. In other frameworks you'd use a thread or processes but there is no such thing in JavaScript (technically there is with web worker and fork in node but that's another story and usually far more complicated than using async api's).

当您想做一些需要时间的事情时,这会产生问题。在其他框架中,您会使用线程或进程,但 JavaScript 中没有这样的东西(从技术上讲,节点中有 web worker 和 fork,但这是另一回事,通常比使用异步 api 复杂得多)。

So when you want to make a http request you can use fetchbut fetch takes some time to finish and your function should not block (has to return something as fast as possible). This is why fetch returns a promise.

因此,当您想发出 http 请求时,您可以使用,fetch但 fetch 需要一些时间才能完成,并且您的函数不应阻塞(必须尽快返回某些内容)。这就是 fetch 返回一个 promise 的原因。

Note that fetch is implemented by browser/node and does run in another thread, only code you write runs in one thread so starting a lot of promises that only run code you write will not speed up anything but calling native async api's in parallel will.

请注意,fetch 是由浏览器/节点实现的,并且确实在另一个线程中运行,只有您编写的代码在一个线程中运行,因此开始许多承诺,只运行您编写的代码不会加快任何速度,但会并行调用本机异步 api。

Before promises async code used callbacks or would return an observable object (like XmlHttpRequest) but let's cover promises since you can convert the more traditional code to a promise anyway.

在 promise 之前异步代码使用回调或返回一个可观察对象(如 XmlHttpRequest),但让我们介绍一下 promise,因为无论如何您都可以将更传统的代码转换为 promise。

A promise is an object that has a thenfunction (and a bunch of stuff that is sugar for then but does the same), this function takes 2 parameters.

promise 是一个具有then函数的对象(以及一堆为 then 做同样的事情),这个函数需要 2 个参数。

  1. Resolve handler: A function that will be called by the promise when the promise resolves (has no errors and is finished). The function will be passed one argument with the resolve value (for http requests this usually is the response).
  2. Reject handler: A function that will be called by the promise when the promise rejects (has an error). This function will be passed one argument, this is usually the error or reason for rejection (can be a string, number or anything).
  1. 解析处理程序:当承诺解决(没有错误并完成)时,承诺将调用的函数。该函数将传递一个带有解析值的参数(对于 http 请求,这通常是响应)。
  2. 拒绝处理程序:当承诺拒绝(有错误)时由承诺调用的函数。这个函数将被传递一个参数,这通常是错误或拒绝的原因(可以是字符串、数字或任何东西)。

Converting callback to promise.

将回调转换为承诺。

The traditional api's (especially nodejs api's) use callbacks:

传统的 api(尤其是 nodejs api)使用回调:

traditionalApi(
  arg
  ,function callback(err,value){ 
    err ? handleFail(err) : processValue(value);
  }
);

This makes it difficult for the programmer to catch errors or handle the return value in a linear way (from top to bottom). It gets even more impossible to try and do things parallel or throttled parallel with error handling (impossible to read).

这使得程序员很难以线性方式(从上到下)捕获错误或处理返回值。尝试与错误处理并行或节流并行处理事情变得更加不可能(无法阅读)。

You can convert traditional api's to promises with new Promise

您可以将传统的 api 转换为承诺 new Promise

const apiAsPromise = arg =>
  new Promise(
    (resolve,reject)=>
      traditionalApi(
        arg,
        (err,val) => (err) ? reject(err) : resolve(val)
      )
  )

async await

异步等待

This is what's called syntax sugar for promises. It makes promise consuming functions look more traditional and easier to read. That is if you like to write traditional code, I would argue that composing small functions is much easier to read. For example, can you guess what this does?:

这就是所谓的 promise 的语法糖。它使 Promise 消费函数看起来更传统且更易于阅读。也就是说,如果您喜欢编写传统代码,我认为组合小函数更容易阅读。例如,你能猜出这是做什么的吗?:

const handleSearch = search =>
  compose([
    showLoading,
    makeSearchRequest,
    processRespose,
    hideLoading
  ])(search)
  .then(
    undefined,//don't care about the resolve
    compose([
      showError,
      hideLoading
    ])
  );

Anayway; enough ranting. The important part is to understand that async awaitdoesn't actually start another thread, asyncfunctions always return a promise and awaitdoesn't actually block or wait. It's syntax sugar for someFn().then(result=>...,error=>...)and looks like:

无论如何; 足够的咆哮。重要的部分是要了解async await实际上并没有启动另一个线程,async函数总是返回一个承诺并且await实际上并没有阻塞或等待。它是语法糖someFn().then(result=>...,error=>...),看起来像:

async someMethod = () =>
  //syntax sugar for:
  //return someFn().then(result=>...,error=>...)
  try{
    const result = await someFn();
    ...
   }catch(error){
     ...
   }
}

The examples allways show try catchbut you don't need to do that, for example:

示例始终显示,try catch但您不需要这样做,例如:

var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
  x=>console.log("never happens, doesn't resolve")
  ,err=>console.warn("got rejected:",err)
);

Any error thrown or awaitreturning a rejected promise will cause the async function to return a rejected promise (unless you try and catch it). Many times it is desirable to just let it fail and have the caller handle errors.

抛出的任何错误或await返回被拒绝的承诺都会导致异步函数返回被拒绝的承诺(除非您尝试捕获它)。很多时候希望让它失败并让调用者处理错误。

Catching errors could be needed when you want the promise to succeed with a special value for rejected promises so you can handle it later but the promise does not technically reject so will always resolve.

当您希望承诺成功并为被拒绝的承诺设置特殊值时,可能需要捕获错误,以便您可以稍后处理它,但承诺在技术上不会拒绝,因此将始终解决。

An example is Promise.all, this takes an array of promises and returns a new promise that resolves to an array of resolved values or reject when any one of them rejects. You may just want to get the results of all promises back and filter out the rejected ones:

一个例子是Promise.all,这需要一个承诺数组并返回一个新的承诺,该承诺解析为一组已解析的值或在其中任何一个拒绝时拒绝。您可能只想获取所有承诺的结果并过滤掉被拒绝的结果:

const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
  urls.map(//map array of urls to array of promises that don't reject
    url =>
      fetch(url)
      .then(
        undefined,//do not handle resolve yet
        //when you handle the reject this ".then" will return
        //  a promise that RESOLVES to the value returned below (new Fail([url,err]))
        err=>new Fail([url,err])
      )
  )
)
.then(
  responses => {
    console.log("failed requests:");
    console.log(
      responses.filter(//only Fail type
        isFail
      )
    );
    console.log("resolved requests:");
    console.log(
      responses.filter(//anything not Fail type
        response=>!isFail(response)
      )
    );
  }
);

回答by Mark Meyer

Your question and the comments suggest you could use a little intuition nudge about the way the event loop works. It reallyis confusing at first, but after a while it becomes second nature.

您的问题和评论表明您可以对事件循环的工作方式稍加直觉。一开始真的很困惑,但过了一段时间它就变成了第二天性。

Rather than thinking about the FINAL VALUE, think about the fact that you have a single thread and you can't stop it — so you want the FUTURE VALUE -- the value on the next or some future event loop. Everything you write that is not asynchronous is going to happen almost immediately — functions return with some value or undefined immediately. There's nothing you can do about. When you need something asynchronously, you need to setup a system that is ready to deal with the async values when they return sometime in the future. This is what events, callbacks, promises (and async/await) all try to help with. If some data is asynchronous, you simply can notuse it in the same event loop.

与其考虑 FINAL VALUE,不如考虑这样一个事实,即您只有一个线程并且无法停止它——因此您需要 FUTURE VALUE——下一个或某个未来事件循环中的值。一切你写的是不是异步将会几乎立即发生-功能与一些值或未定义返回立即。你无能为力。当您需要异步操作时,您需要设置一个系统,以便在异步值在未来某个时间返回时处理它们。这就是事件、回调、promise(和 async/await)都试图帮助解决的问题。如果某些数据是异步的,您就不能在同一个事件循环中使用它。

So what do you do?

所以你会怎么做?

If you want a pattern where you create an instance, call init()and then some function that further process it, you simply need to setup a system that does the processing when the data arrives. There are a lot of ways to do this. Here's one way that's a variation on your class:

如果您想要一个模式,您可以在其中创建一个实例,调用init()然后进一步处理它的一些函数,您只需要设置一个系统,在数据到达时进行处理。有很多方法可以做到这一点。这是您班级变化的一种方式:

function someAsync() {
  console.log("someAsync called")
  return new Promise(resolve => {
    setTimeout(() => resolve(Math.random()), 1000)
  })
}

class Posts {
  constructor(url) {
    this.ready = false
    this.data = "uninitilized"
    this.url = url
  }
  init() {
    this.data = someAsync()

  }
  time100() {
    // it's important to return the promise here
    return this.data.then(d => d * 100)
  }
}

let p = new Posts()
p.init()
processData(p)
// called twice to illustrate point
processData(p)

async function processData(posts) {
  let p = await posts.time100()
  console.log("randomin * 100:", p)
}

init()saves the promise returned from someAsync(). someAsync()could be anything that returns a promise. It saves the promise in an instance property. Now you can call then()or use async/await to get the value. It will either immediately return the value if the promise has already resolved or it will deal with it when it has resolved. I called processData(p)twice just to illustrate that it doesn't calle the someAsync()twice.

init()保存从someAsync(). someAsync()可以是任何返回承诺的东西。它将承诺保存在实例属性中。现在您可以调用then()或使用 async/await 来获取值。如果承诺已经解决,它将立即返回该值,或者在解决后处理它。我打了processData(p)两次电话只是为了说明它没有someAsync()打过两次电话。

That's just one pattern. There are a lot more — using events, observables, just using then()directly, or even callbacks which are unfashionable, but still can be useful.

那只是一种模式。还有更多——使用事件、可观察对象、直接使用then(),甚至是不流行但仍然有用的回调。

回答by Nandu Kalidindi

NOTE:Wherever you use awaitit has to be inside an asyncfunction.

注意:无论您await在哪里使用它,都必须在async函数内部。

Check out the UPDATED FIDDLE

查看更新的 FIDDLE

You need to use await myFunc()to get the value you expect from getPostByIdbecause an async function always returns a promise.

您需要使用await myFunc()来获取您期望的值,getPostById因为异步函数总是返回一个承诺。

This sometimes is very frustrating as the whole chain needs to be converted into asyncfunctions but that's the price you pay for converting it to a synchronous code, I guess. I am not sure if that can be avoided but am interested in hearing from people who have more experience on this.

这有时非常令人沮丧,因为需要将整个链转换为async函数,但我猜这是将其转换为同步代码所要付出的代价。我不确定这是否可以避免,但我有兴趣听取对此有更多经验的人的意见。

Try out the below code in your console by copying over the functions and then accessing finaland await final.

通过复制函数然后访问final和 ,在您的控制台中尝试以下代码await final

NOTE:

笔记:

An async function CANcontain an await expression. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

异步函数CAN包含一个AWAIT表达。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

There is no rule that is must have await in order to even declare an async function. The example below uses an async function without await just to show that an async function always returns a promise.

没有规则必须有 await 才能声明异步函数。下面的例子使用了一个没有 await 的异步函数,只是为了表明一个异步函数总是返回一个承诺。

const sample = async () => {
  return 100;
}

// sample() WILL RETURN A PROMISE AND NOT 100
// await sample() WILL RETURN 100

const init = async (num) => {
  return new Promise((resolve, reject) => {
    resolve(num);
  });
}

const myFunc = async (num) => {
  const k = await init(num);
  return k;
}

// const final = myFunc();
// final; This returns a promise
// await final; This returns the number you provided to myFunc