JavaScript ES6 类是不是可用于异步代码库?

Posted

技术标签:

【中文标题】JavaScript ES6 类是不是可用于异步代码库?【英文标题】:Are JavaScript ES6 Classes of any use with asynchronous code bases?JavaScript ES6 类是否可用于异步代码库? 【发布时间】:2016-09-30 01:51:44 【问题描述】:

ES6 Classes 作为一种组织模式可以为异步代码提供什么。下面是一个 ES7 async/await 的例子,一个 ES6 类可以有一个异步方法,或者 ES7 中的构造函数吗?

我能做到吗:

class Foo 
    async constructor() 
        let res = await gethtml();
        this.res = res
    

如果不是,构造函数应该如何工作?

class Foo 
    constructor() 
        getHTML().then( function (res) 
            this.res = res
        
    

如果这些模式都不起作用,ES6 class 中的构造函数(以及类)是否可以支持任何形式的对对象状态进行操作的异步?或者,它们是否仅适用于纯同步代码库?上面的例子在构造函数中,但它们不需要......将问题推低一层......

class Foo 
    myMethod () 
      /* Can I do anything async here */
    

或者,使用吸气剂...

class Foo 
    get myProp() 
        /* Is there any case that this is usefully asynchronous */
    

我能想到的唯一示例是在同一个方法/构造函数/getter 中并行运行某些东西,但要在得出结论之前解决整个问题。我只是感到困惑,因为似乎对完全异步库的所有推动,这只会使事情变得混乱。除了教科书的例子,我找不到它们有用的应用程序。

【问题讨论】:

可以从构造函数返回一个用实例解析的promise,从而在实例初始化后让你访问它。 @KevinB 我确实想到了这个想法,但听起来很可怕。 我也不是特别喜欢,但是……还有什么办法呢?某处必须有一个回调,它要么是一个承诺,要么是一个作为参数传递的回调。 async/await 还是有回调的,只是你没看到。 @KevinB 我认为你是对的,我认为类只是一个即将成为反模式的东西。使用异步代码库进入它们将是非常困难,摆脱它们编写真正有用的异步库将是非常困难,并且从未来来看对于来电者来说,写起来会非常尴尬,await new Foo(url); 我已经把问题说得更广泛了,我不想假设我知道答案。让我们拭目以待,看看是否有人鼓起其他任何东西。如果没有,我会赏金。 【参考方案1】:

我可以async constructor()

不,这是语法错误 - 就像 constructor* ()。构造函数是一种不返回任何东西(没有承诺,没有生成器)的方法,它只初始化实例。

如果不是这样,构造函数应该如何工作

这样的构造函数根本不应该存在,见Is it bad practice to have a constructor function return a Promise?

ES6 类可以支持任何形式的对对象状态进行操作的异步吗?或者,它们是否仅适用于纯同步代码库?

是的,您可以在类上使用异步方法(即使使用建议的async 语法),getter 也可以返回 Promise。

但是,您需要决定在某个异步进程仍处于活动状态时调用方法时应该发生什么。如果您希望它对您的所有操作进行排序,您应该将实例的状态存储在一个可以链接到该序列结束的 Promise 中。或者,如果您想允许并行操作,最好的方法是使您的实例不可变并为另一个实例返回一个承诺。

【讨论】:

这是一个非常好的答案,我可以对其进行编辑以提供一个通过 Promise 链接到自身的类的示例。我仍然不满意这是一个好主意。它看起来更像是一种反模式,仍然。 @EvanCarroll:这是否是一个好主意取决于你在做什么。 ES6 class 语法并没有带来任何新东西,对象(实例)中的异步和状态一直很复杂。 “构造函数是一种不返回任何东西的方法(没有承诺,没有生成器),它只初始化实例。”:不完全正确。 ES6 中的构造函数可以返回它喜欢的任何对象。考虑一下:class Foo constructor(input) return new Promise(resolve => resolve(input)); new Foo('bar').then(res => console.log(res)) @ChenEshchar 是的,你可以在构造函数中做一些可怕的事情,但是you shouldn't。也许我应该写“…是一种方法,其工作是…”。【参考方案2】:

类可用于安排异步任务的另一种方式是独占使用static methods。

class Organizer 
    static async foo() 
        const data = await this.bar();
        data.key = value;
        return data;
    
    static async bar() 
        return foo:1, bar:2
    
;

Organizer.foo();

当然,这与创建一个简单的对象文字或一个新文件并包含它没有什么不同,只是你可以更干净地extend它。

【讨论】:

你永远不应该那样做。只需使用对象字面量(您也可以使用Object.createObject.assign 非常简单地扩展它们)。或者,给定 ES6 模块,只需使用多个命名导出 - 扩展这些更容易,就像寄生继承。 永远不要......除非你还想创建 Organizer 的实例【参考方案3】:

ECMAScript 2017 旨在成为异步方法的类。

调用另一个异步或返回承诺的函数是单行的!

无论延迟执行如何,高表达的代码从上到下不间断地读取

如果您有回调、替代错误处理程序、并行执行或其他未满足的需求,请在函数体中实例化 Promise。最好将代码放在函数体中而不是在 Promise 执行器中,并注意没有 try-catch 包装回调代码:在那里几乎什么都不做。

异步方法可以返回一个承诺、一个常规值或抛出

Node.js 人曾经喜欢的回调 api,现在我们会痛恨:它们都必须被封装在 Promise 中

async/await 的美妙之处在于错误会隐式冒出

class MyClass 
  async doEverything() 
    const sumOfItAll = await http.scrapeTheInternet() +
      await new Promise((resolve, reject) =>
        http.asyncCallback((e, result) => !e ? resolve(result) : reject(e)))
    return this.resp = sumOfItAll
  

如果仅限于 ECMAScript 2015 并且没有异步,则返回承诺值:

class ES2015 
  fetch(url) 
    return new Promise((resolve, reject) =>
      http.get(url, resolve).on('error', reject))
      .then(resp => this.resp = resp) // plain ECMAScript stores result
      .catch(e =>  // optional internal error handler
        console.error(e.message)
        throw e // if errors should propagate
      )
  

这个 ECMAScript 2015 版本是您真正要问的,任何期望的行为都可以使用返回的 promise 构造进行编码。

如果您真的非常想在构造函数中执行承诺,最好传入 then-catch 函数或提供一些回调构造,以便消费者可以对承诺履行或拒绝采取行动。在构造函数中,在执行实际工作之前等待 nextTick/.then 也是一个好习惯。

每一个承诺都需要一个最终的收获,否则会有麻烦

【讨论】:

【参考方案4】:

无法将“异步”添加到构造函数的解决方法。返回异步函数时,它就像返回一个 Promise,因此构造函数本身不必是异步的。

class Foo 
  constructor() 
    return this.init()
  
  async init() 
    this.res = await getHTML()
    return this
  

const foo = await new Foo()

甚至更短,但需要使用承诺

class Foo 
  constructor() 
    return new Promise(async resolve => 
      this.res = await getHTML()
      resolve(this)
    )
  

const foo = await new Foo()

【讨论】:

【参考方案5】:

这是一个较晚的响应,但您的第二个示例不起作用的原因是上下文错误。当您将function () 作为参数传递给Promise.prototype.then() 时,函数内部的词法this 将是函数本身,而不是类。这就是为什么设置 this.res 似乎什么都不做的原因:this,在这种情况下,指的是函数自己的范围。

javascript 中有几种访问外部作用域的方法,经典的一种(在 ES5 代码中经常看到)是:

class Foo 
  constructor() 
    var _this = this

    getHTML().then(function (res) 
      _this.res = res
    )
  

通过引用类this,您可以在内部范围内访问它。

ES6 的做法是使用arrow functions,它不会创建新的作用域,而是“保留”当前的作用域。

class Foo 
  constructor() 
    getHTML().then(res => this.res = res)
  

除了上下文问题,我认为这仍然不是最佳异步模式,因为您无法知道getHTML() 何时完成,或者更糟的是,失败了。这个问题用async functions 优雅地解决了。虽然你不能创建一个async constructor () ... ,但你可以在构造函数中发起一个promise,并在依赖它的函数中await它。

Example gist 类构造函数中的异步属性。

【讨论】:

以上是关于JavaScript ES6 类是不是可用于异步代码库?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript ES6 promiss的理解。

es6异步操作

ES6 异步编程——Promise

es6-异步应用

javascript ES6 新特性之 扩展运算符 三个点 ...

JavaScript中ES6新特性-Generator异步方案