Ale*_*ggs 108 javascript node.js async-await
目前,我正在尝试async/await在类构造函数中使用.这样我就可以获得e-mail我正在研究的Electron项目的自定义标签.
customElements.define('e-mail', class extends HTMLElement {
async constructor() {
super()
let uid = this.getAttribute('data-uid')
let message = await grabUID(uid)
const shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.innerHTML = `
<div id="email">A random email message has appeared. ${message}</div>
`
}
})
Run Code Online (Sandbox Code Playgroud)
但是,目前该项目不起作用,出现以下错误:
Class constructor may not be an async method
Run Code Online (Sandbox Code Playgroud)
有没有办法绕过这个,以便我可以在其中使用async/await?而不是要求回调或.then()?
sle*_*man 181
这永远不会奏效.
该async关键字允许await在标记为的函数中使用,async但它也将该函数转换为promise生成器.所以标记的函数async将返回一个promise.另一方面,构造函数返回它正在构造的对象.因此,我们有一种情况,你想要同时返回一个对象和一个承诺:一个不可能的情况.
你只能使用async/await来使用promises,因为它们本质上是promises的语法糖.您不能在构造函数中使用promises,因为构造函数必须返回要构造的对象,而不是promise.
有两种设计模式可以克服这个问题,这两种模式都是在承诺出现之前发明的.
使用init()功能.这有点像jQuery的.ready().您创建的对象只能在其自己init或ready函数内使用:
用法:
var myObj = new myClass();
myObj.init(function() {
// inside here you can use myObj
});
Run Code Online (Sandbox Code Playgroud)
执行:
class myClass {
constructor () {
}
init (callback) {
// do something async and call the callback:
callback.bind(this)();
}
}
Run Code Online (Sandbox Code Playgroud)使用构建器.我没有看到这在javascript中使用得太多但是当需要异步构造对象时,这是Java中更常见的解决方法之一.当然,构造需要大量复杂参数的对象时使用构建器模式.这正是异步构建器的用例.区别在于异步构建器不返回对象,而是返回该对象的承诺:
用法:
myClass.build().then(function(myObj) {
// myObj is returned by the promise,
// not by the constructor
// or builder
});
// with async/await:
async function foo () {
var myObj = await myClass.build();
}
Run Code Online (Sandbox Code Playgroud)
执行:
class myClass {
constructor (async_param) {
if (typeof async_param === 'undefined') {
throw new Error('Cannot be called directly');
}
}
static build () {
return doSomeAsyncStuff()
.then(function(async_result){
return new myClass(async_result);
});
}
}
Run Code Online (Sandbox Code Playgroud)
使用async/await实现:
class myClass {
constructor (async_param) {
if (typeof async_param === 'undefined') {
throw new Error('Cannot be called directly');
}
}
static async build () {
var async_result = await doSomeAsyncStuff();
return new myClass(async_result);
}
}
Run Code Online (Sandbox Code Playgroud)注意:虽然在上面的例子中我们使用了异步构建器的promises,但严格来说它们并不是必需的.您可以轻松编写接受回调的构建器.
这与异步构造函数没有任何关系,但与关键字this实际意味着什么(对于来自自动解析方法名称的语言的人来说,这可能有点令人惊讶,也就是说,不需要this关键字的语言).
的this关键字是指实例化的对象.不是班级.因此,您通常不能使用this内部静态函数,因为静态函数不绑定到任何对象,而是直接绑定到类.
也就是说,在以下代码中:
class A {
static foo () {}
}
Run Code Online (Sandbox Code Playgroud)
你做不到:
var a = new A();
a.foo() // NOPE!!
Run Code Online (Sandbox Code Playgroud)
相反,你需要将其称为:
A.foo();
Run Code Online (Sandbox Code Playgroud)
因此,以下代码将导致错误:
class A {
static foo () {
this.bar(); // you are calling this as static
// so bar is undefinned
}
bar () {}
}
Run Code Online (Sandbox Code Playgroud)
要修复它,您可以bar使用常规函数或静态方法:
function bar1 () {}
class A {
static foo () {
bar1(); // this is OK
A.bar2(); // this is OK
}
static bar2 () {}
}
Run Code Online (Sandbox Code Playgroud)
Dow*_*oat 84
你绝对可以做到这一点.基本上:
class AsyncConstructor {
constructor() {
return (async () => {
// All async code here
this.value = await asyncFunction();
return this; // when done
})();
}
}
Run Code Online (Sandbox Code Playgroud)
创建类使用:
let instance = await new AsyncConstructor();
Run Code Online (Sandbox Code Playgroud)
注意:如果需要使用super,则无法在异步回调中调用它.你必须在它之外调用它,所以这个解决方案不是 100%完美,但在我看来它是非常惯用的,我在我的代码中一直使用它.
Vid*_*dar 10
因为异步函数是承诺,你可以在你的类上创建一个静态函数,它执行一个返回类实例的异步函数:
class Yql {
constructor () {
// Set up your class
}
static init () {
return (async function () {
let yql = new Yql()
// Do async stuff
await yql.build()
// Return instance
return yql
}())
}
async build () {
// Do stuff with await if needed
}
}
async function yql () {
// Do this instead of "new Yql()"
let yql = await Yql.init()
// Do stuff with yql instance
}
yql()
Run Code Online (Sandbox Code Playgroud)
let yql = await Yql.init()从异步函数调用 with 。
Unlike others have said, you can get it to work.
JavaScript classes can return literally anything from their constructor , even an instance of another class. So, you might return a Promise from the constructor of your class that resolves to its actual instance.
Below is an example:
export class Foo {
constructor() {
return (async () => {
// await anything you want
return this; // Return the newly-created instance
})();
}
}
Run Code Online (Sandbox Code Playgroud)
Then, you'll create instances of Foo this way:
const foo = await new Foo();
Run Code Online (Sandbox Code Playgroud)
您可以创建一个async init() {... return this;}方法,然后new MyClass().init()在您通常只说new MyClass().
这并不干净,因为它依赖于使用您的代码的每个人以及您自己,总是像这样实例化对象。但是,如果您仅在代码中的一两个特定位置使用此对象,则可能没问题。
但是,由于 ES 没有类型系统,因此出现了一个重大问题,因此如果您忘记调用它,您只是返回了,undefined因为构造函数什么都不返回。哎呀。更好的是做这样的事情:
最好的做法是:
class AsyncOnlyObject {
constructor() {
}
async init() {
this.someField = await this.calculateStuff();
}
async calculateStuff() {
return 5;
}
}
async function newAsync_AsyncOnlyObject() {
return await new AsyncOnlyObject().init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}
Run Code Online (Sandbox Code Playgroud)
但是,您可能会不小心执行新的 AsyncOnlyObject,您可能应该创建Object.create(AsyncOnlyObject.prototype)直接使用的工厂函数:
async function newAsync_AsyncOnlyObject() {
return await Object.create(AsyncOnlyObject.prototype).init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}
Run Code Online (Sandbox Code Playgroud)
但是,如果您想在许多对象上使用此模式……您可以将其抽象为装饰器或您在定义 like 后(详细地,呃)调用的东西postProcess_makeAsyncInit(AsyncOnlyObject),但在这里我将使用extends它,因为它有点适合子类语义(子类是父类+额外的,因为它们应该遵守父类的设计契约,并且可以做额外的事情;如果父类不是异步的,异步子类会很奇怪,因为它不能被初始化为相同的道路):
class AsyncObject {
constructor() {
throw new Error('classes descended from AsyncObject must be initialized as (await) TheClassName.anew(), rather than new TheClassName()');
}
static async anew(...args) {
var R = Object.create(this.prototype);
R.init(...args);
return R;
}
}
class MyObject extends AsyncObject {
async init(x, y=5) {
this.x = x;
this.y = y;
// bonus: we need not return 'this'
}
}
MyObject.anew('x').then(console.log);
// output: MyObject {x: "x", y: 5}
Run Code Online (Sandbox Code Playgroud)
(不要在生产中使用:我没有考虑过复杂的场景,例如这是否是为关键字参数编写包装器的正确方法。)
根据您的评论,您可能应该执行所有其他带有资产加载的HTMLElement所做的事情:使构造函数启动侧载操作,根据结果生成加载或错误事件。
是的,这意味着使用诺言,但同时也意味着“以与其他所有HTML元素相同的方式进行操作”,因此您处于良好的状态。例如:
var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";
Run Code Online (Sandbox Code Playgroud)
这将启动源资产的异步负载,该负载成功时将终止于源资产,onload错误时将终止于onerror。因此,让自己的班级也这样做:
class EMailElement extends HTMLElement {
constructor() {
super();
this.uid = this.getAttribute('data-uid');
}
setAttribute(name, value) {
super.setAttribute(name, value);
if (name === 'data-uid') {
this.uid = value;
}
}
set uid(input) {
if (!input) return;
const uid = parseInt(input);
// don't fight the river, go with the flow
let getEmail = new Promise( (resolve, reject) => {
yourDataBase.getByUID(uid, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
// kick off the promise, which will be async all on its own
getEmail()
.then(result => {
this.renderLoaded(result.message);
})
.catch(error => {
this.renderError(error);
});
}
};
customElements.define('e-mail', EmailElement);
Run Code Online (Sandbox Code Playgroud)
然后,使renderLoaded / renderError函数处理事件调用和阴影dom:
renderLoaded(message) {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">A random email message has appeared. ${message}</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onload(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('load', ...));
}
renderFailed() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">No email messages.</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onerror(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('error', ...));
}
Run Code Online (Sandbox Code Playgroud)
另请注意,我将您更改id为class,因为除非您编写一些怪异的代码以仅允许<e-mail>页面上元素的单个实例,否则您将无法使用唯一标识符,然后将其分配给一堆元素。
该线程中有许多好的(和一些坏的)注释......但没有一个真正涵盖整个故事和 TypeScript。这是我的看法。
此问题有 2 个解决方法。
async function makeAPI() {
await youCanGoAsyncHere()
async function fetchFirst() {}
async function fetchSecond() {}
return {
fetchFirst,
fetchSecond,
}
}
Run Code Online (Sandbox Code Playgroud)
用闭包复制一些继承模式很混乱,但对于更简单的情况,它通常就足够了。
protected构造函数的工厂 和init:import * as U from "lib/utils"
class API {
data!: number // the use of ! here is fine
// we marked the constructor "protected" + we call `init` in `make`
// assuming we don't like multiple `data?.something` checks
protected constructor() {
...
}
protected async init() {
await youCanGoAsyncHere()
this.data = 123 // assume other methods depend on this data
}
fetchFirst() {}
fetchSecond() {}
static async make() {
const api = new API()
await api.init()
return api
}
}
const t = await Test.make()
console.log(t.data)
Run Code Online (Sandbox Code Playgroud)
这里的主要缺点是 JS/TS 中静态方法与泛型的继承有些削弱。
| 归档时间: |
|
| 查看次数: |
80176 次 |
| 最近记录: |