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

Jon*_*ues 7 javascript async-await ecmascript-6

我有一个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 )
  }
}  
Run Code Online (Sandbox Code Playgroud)

直截了当,除了我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
Run Code Online (Sandbox Code Playgroud)

但它只会被打印到控制台:我如何使用它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 )
}
Run Code Online (Sandbox Code Playgroud)

myFunc()返回Promise但不是最终值.我已经阅读了关于这个主题的几个相关帖子,但它们都给出了记录的例子,从未返回.

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

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

编辑:

尝试解释Promises在主执行循环方面有很多好的答案.经过大量的视频和其他好的阅读后,这就是我现在所理解的:

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

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

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

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

因此,它本身会返回一个Promise,可以使用await它来嵌入一个async函数中,它可以用于await等...

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

谢谢您的帮助 !

HMR*_*HMR 9

至于你的评论; 我会把它添加为答案.

您在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
  )
);
Run Code Online (Sandbox Code Playgroud)

在控制台中运行它,你看到我的意思是阻止.

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

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

请注意,fetch是由浏览器/节点实现的,并且在另一个线程中运行,只有您编写的代码在一个线程中运行,因此启动大量只运行您编写的代码的promise将不会加速任何事情,除了并行调用本机异步api之外.

在承诺异步代码使用回调之前或者会返回一个可观察对象(比如XmlHttpRequest)但是让我们覆盖承诺,因为你可以将更传统的代码转换为承诺.

promise是一个具有一个then函数的对象(以及一堆当时为糖但却相同的东西),这个函数需要2个参数.

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

将回调转换为承诺.

传统的api(特别是nodejs api)使用回调:

traditionalApi(
  arg
  ,function callback(err,value){ 
    err ? handleFail(err) : processValue(value);
  }
);
Run Code Online (Sandbox Code Playgroud)

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

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

const apiAsPromise = arg =>
  new Promise(
    (resolve,reject)=>
      traditionalApi(
        arg,
        (err,val) => (err) ? reject(err) : resolve(val)
      )
  )
Run Code Online (Sandbox Code Playgroud)

异步等待

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

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

Anayway; 足够的咆哮.重要的是要理解async await实际上并没有启动另一个线程,async函数总是返回一个promise并且await实际上不会阻塞或等待.这是语法糖someFn().then(result=>...,error=>...),看起来像:

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

示例总是显示,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)
);
Run Code Online (Sandbox Code Playgroud)

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

当您希望承诺成功使用特殊值来拒绝承诺时,可能需要捕获错误,以便您可以在以后处理它,但承诺在技术上不会拒绝,因此将始终解决.

一个例子是Promise.all,它接受一个promises数组并返回一个新的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)
      )
    );
  }
);
Run Code Online (Sandbox Code Playgroud)