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

新手上路,请多包涵

我有一个 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 )
  }
}

直截了当,除了我在 --- init() async/await 机制。现在,此代码将正常工作:

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

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

但它只会打印到控制台中:How could I use allPosts.getPostById(4) as a return of a function?

喜欢:

 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() 返回一个 Promise 但不是最终值。我已经阅读了几篇关于该主题的相关文章,但它们都给出了日志记录的示例,永不返回。

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

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

编辑:

很多很好的答案试图解释什么是关于主执行循环的承诺。在看了很多视频和其他好的读物之后,这是我现在的理解:

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

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

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

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

因此它自己返回一个 Promise,可与 await 一起使用,它应该嵌入到 async 函数中,该函数将与 await c2eb…

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

谢谢您的帮助 !

原文由 Jona Rodrigues 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 631
2 个回答

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

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

在浏览器中阻止代码的一个很好的例子是 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
  )
);

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

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

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

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

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

promise 是一个对象,它有一个 then 函数(以及一堆对 then 有用但作用相同的东西),这个函数有 2 个参数。

  1. Resolve handler:当 promise 解析时(没有错误并且完成)将由 promise 调用的函数。该函数将传递一个带有解析值的参数(对于 http 请求,这通常是响应)。
  2. 拒绝处理程序:当 promise 拒绝(有错误)时将由 promise 调用的函数。这个函数将传递一个参数,这通常是错误或拒绝的原因(可以是字符串、数字或任何东西)。

将回调转换为承诺。

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

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

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

您可以使用 new Promise

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

异步等待

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

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

无论如何;足够的咆哮。重要的部分是要了解 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){
     ...
   }
}

示例始终显示 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)
);

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

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

一个例子是 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)
      )
    );
  }
);

原文由 HMR 发布,翻译遵循 CC BY-SA 3.0 许可协议

您的问题和评论表明您可以使用一点直觉来推动事件循环的工作方式。起初 确实 令人困惑,但过了一会儿它就变成了第二天性。

与其考虑 FINAL VALUE,不如考虑这样一个事实,即您只有一个线程并且无法停止它——因此您需要 FUTURE VALUE——下一个或某个未来事件循环的值。你写的所有非异步的东西几乎都会立即发生——函数 立即 返回一些值或未定义。你无能为力。当你需要一些异步的东西时,你需要设置一个系统,当它们在未来某个时候返回时准备好处理异步值。这就是事件、回调、承诺(和异步/等待)都试图提供的帮助。如果一些数据是异步的,你就 不能 在同一个事件循环中使用它。

所以你会怎么做?

如果您想要一个创建实例的模式,请调用 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() 保存从 someAsync() 返回的承诺。 someAsync() 可以是返回承诺的任何东西。它将承诺保存在实例属性中。现在您可以调用 then() 或使用 async/await 来获取值。如果承诺已经解决,它将立即返回值,或者在解决后处理它。我调用了 processData(p) 两次只是为了说明它不会调用 someAsync() 两次。

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

原文由 Mark 发布,翻译遵循 CC BY-SA 4.0 许可协议

推荐问题