JavaScript ES6类在异步代码库中有用吗?

97
作为一种组织模式,ES6 Classes 能为异步代码提供什么帮助?以下是一个 ES7 async/await 的示例,ES6 类是否可以拥有异步方法或构造函数?
我可以这样做吗:
class Foo {
    async constructor() {
        let res = await getHTML();
        this.res = res
    }
}

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

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

如果这两种模式都不起作用,那么 ES6 中的构造函数(以及类)是否支持任何操作对象状态的异步形式?或者,它们仅适用于纯同步代码基础?上述示例位于构造函数中,但它们不需要在其中。将问题再向下推一层。
class Foo {
    myMethod () {
      /* Can I do anything async here */
    }
}

或者,使用 getter 方法...

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

我能想到的唯一例子就是在同一个方法/构造函数/获取器中并行运行某些内容,但要在结束之前解决整个问题。我感到困惑的是,随着完全异步库的推出,这似乎只会使事情更加混乱。除了教科书上的例子外,我找不到它们有用的应用程序。


你可以从构造函数返回一个 Promise,该 Promise 会在实例化完成后将实例解析出来,因此你就可以访问该实例了。 - Kevin B
@KevinB 我也有想过这个想法,但听起来很糟糕。 - Evan Carroll
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kevin B
@KevinB 我认为你是对的,我认为类只是一个即将成为反模式的东西。在异步代码库中使用它们将会非常困难,从中退出以编写真正有用的异步库也将非常困难,而从调用者的角度来看,编写 await new Foo(url); 将会非常尴尬。我已经将问题更广泛化了,我不想假设我知道答案。让我们等待并看看是否还有其他人提出任何问题。如果没有,我会设置悬赏。 - Evan Carroll
5个回答

76

我能使用async constructor()吗?

不行,这是语法错误,就像constructor* ()一样。构造函数是一个不返回任何东西的方法(没有promise,没有generator),它只初始化实例。

那么,如果不行,应该如何编写具有此功能的构造函数呢?

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

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

是的,您可以在类上使用异步方法(甚至使用提议中的async语法),并且getter也可以返回Promise。

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


2
这是一个非常好的答案,我可以编辑它以提供一个通过 promises 链接到自身的类的示例。但我仍然不满意这是否是一个好主意。它似乎更像是一种反模式。 - Evan Carroll
5
这取决于你要做什么。ES6的class语法并没有带来新的东西,对象(实例)中的异步性和状态一直很复杂。 - Bergi
1
“构造函数”是一种不返回任何东西的方法(没有promise,没有generator),它仅初始化实例。这并不完全正确。在ES6中,构造函数可以返回任何它喜欢的对象。请考虑以下示例: class Foo { constructor(input) { return new Promise(resolve => resolve(input)); } } new Foo('bar').then(res => console.log(res)) - Chen Eshchar
1
@ChenEshchar 是的,你可以在构造函数中做一些可怕的事情,但是你不应该这样做。也许我应该写成“*...是一个工作职责是...的方法*”。 - Bergi

32

使用静态方法是类别的另一种有用方式,可以用于排列异步任务,详见静态方法和属性

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

当然,这与创建简单的对象文字或新文件并包含它没有区别,只不过你可以更加清晰地扩展它。


6
在我看来,你永远不应该这样做。只需使用对象字面量(也可以使用Object.createObject.assign轻松扩展它们)。或者,考虑到ES6模块,只需使用多个命名导出 - 扩展它们甚至更容易,就像寄生继承一样。 - Bergi
5
除非您还想创建Organizer的实例,否则永远不要这样做。 - neaumusic

18

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

调用另一个异步或返回promise的函数只需要一行代码!

即使存在延迟执行,高度表达力的代码也可以毫不中断地从上到下阅读。

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

异步方法可以返回promise、常规值或抛出异常。

Node.js人们曾经喜欢的回调api现在会被我们憎恶:它们必须全部封装在promises中。

异步/等待的美妙之处在于错误会隐式地冒泡上升。

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,且没有异步操作,则返回 Promise 值:

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 构造进行编码。

如果你真的非常希望在构造函数中执行 Promise,最好提供 then-catch 函数或提供一些回调结构,以便消费者可以在 Promise 完成或拒绝时采取行动。在构造函数中,还应该在进行实际工作之前等待 nextTick/.then,这是一个很好的做法。

每个 Promise 都需要一个最终的 catch,否则会出现问题。


8
无法在构造函数中添加"async"的解决方法。返回一个异步函数就像返回一个承诺,因此构造函数本身不必是异步的。
class Foo {
  constructor() {
    return this.init()
  }
  async init() {
    this.res = await getHTML()
    return this
  }
}
const foo = await new Foo()

更短,但需要使用 Promise

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

2
这是一个晚回复,但你的第二个例子不起作用的原因是上下文错误。当你将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的方法是使用箭头函数,它们不会创建新的作用域,而是“保留”当前作用域。
class Foo {
  constructor() {
    getHTML().then(res => this.res = res)
  }
}

除了上下文的考虑之外,我认为这仍然不是一种最佳的异步模式,因为你无法知道getHTML()何时完成,或者更糟糕的是,失败了。这个问题可以通过异步函数来优雅地解决。虽然你不能创建一个async constructor () { ... },但你可以在构造函数中初始化一个promise,并在依赖它的函数中使用await
以下是一个类构造函数中异步属性的示例代码

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接