【问题标题】:Async function not returning value, but console.log() does: how to do? [duplicate]异步函数没有返回值,但是 console.log() 确实:怎么办? [复制]
【发布时间】:2018-05-19 18:17:38
【问题描述】:

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

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

喜欢:

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

Here is a fiddle 包括两种处理init() 的方法:使用Promise 和使用async/await。无论我尝试什么,我都无法使用getPostById(id) 的最终值。

这篇文章的问题是:我怎样才能创建一个函数来返回getPostById(id) 的值?

编辑:

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

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

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

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

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

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

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

感谢您的帮助!

【问题讨论】:

  • 见:stackoverflow.com/questions/14220321/… 基本上,你不能。你要么等待承诺,要么在异步函数中使用await。
  • 在await allPosts 之后的console.log 中是否可以看到allPosts.data?
  • @NanduKalidindi yes console.log(allPosts.data) 将正确的值打印到控制台。但是,仍然可以返回它。 @Mark_M 是的,我读过这篇文章,但恐怕这个问题并没有得到真正的回答:它只是嵌入了关于异步代码的课程......我需要变得更实用一些。如果你告诉我这是不可能的:如果我们不能使用它们的结果,为什么还存在 Promises?啊哈哈
  • 我不确定你是否理解 Promise,你不能返回一个值,因为该值取决于调用你的函数时尚不可用的东西。这就是为什么你可以使用回调和承诺,函数必须返回一些东西,所以它返回一个可以解析或拒绝的值的承诺。 async await 语法表明函数等待,但实际上它没有,async 函数总是返回一个承诺。
  • @HMR 我会说使用await 的函数代码确实会等待,但函数的调用者 不会。 (它必须使用await(或then)本身)。

标签: javascript ecmascript-6 async-await


【解决方案1】:

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

查看UPDATED FIDDLE

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

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

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

注意:

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

没有规则必须等待才能声明异步函数。 下面的例子使用了一个不带 await 的异步函数,只是为了说明一个异步函数总是返回一个 Promise。

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

【讨论】:

  • 你不能在异步函数之外await final。
  • 是的,这就是为什么我说需要将整个函数链转换为async函数。这也是我在网络检查器中建议上述 sn-p 只是为了了解一下的原因。好吧,如果您确实阅读并投了反对票,那么为您干杯,先生!
  • 对不起@NanduKalidindi 你看到我的fiddle了吗?如果我错了,请纠正我,但它完全符合您的建议,这是行不通的,不是吗?
  • 请检查我的回答中的更新小提琴。
  • 好的,但是由于您已经返回了一个承诺,因此不需要异步。你可以这样做 const init = async num=>num 这将返回 num 的承诺,而无需额外的代码。
【解决方案2】:

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

与其考虑 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 来获取值。如果 Promise 已经解决,它将立即返回该值,或者在解决后处理它。我调用processData(p) 两次只是为了说明它没有调用someAsync() 两次。

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

【讨论】:

  • 非常感谢您花时间在这里:非常感谢。我想我的问题来自对 async/await 的理解。在MDN 中明确指出“等待暂停执行”......对我来说,这听起来像是函数应该暂停,等待对象被获取,如果需要,将结果包含在一个变量中,然后我们可以使用这个变量在主循环中!! PAUSE = PAUSE...它应该暂停循环,就是这样。至少我是这么看的,为什么它会给我带来麻烦。
  • 我同意 - 这是一种令人困惑的描述方式。
  • @Mark_M Waooo,很好的回答亲爱的摄影师,最好的!
【解决方案3】:

至于你的评论;我会添加它作为答案。

您用 JavaScript 编写的代码在一个线程上运行,这意味着如果您的代码实际上可以等待某事,它将阻止您的任何其他代码执行。 JavaScript 的事件循环在this video 中有很好的解释,如果你喜欢阅读this page。

在浏览器中阻止代码的一个很好的例子是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 返回承诺的原因。

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

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

promise 是一个具有then 函数的对象(以及一堆当时的糖,但作用相同),该函数有 2 个参数。

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

将回调转换为承诺。

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

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

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

您可以使用 new Promise 将传统 api 转换为 Promise

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

异步等待

这就是所谓的 Promise 语法糖。它使 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 成功并为被拒绝的 promise 设置一个特殊值时,可能需要捕获错误,以便您稍后处理它,但从技术上讲,promise 不会拒绝,因此总是会解决。

一个例子是Promise.all,它接受一组promise并返回一个新的promise,该promise解析为一组已解析的值或当其中任何一个拒绝时拒绝。您可能只想取回所有 promise 的结果并过滤掉被拒绝的结果:

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

【讨论】:

    猜你喜欢
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 2016-01-14
    相关资源
    最近更新 更多