Для очень специфического случая инициализации асинхронного ресурса есть несколько шаблонов проектирования, которые вы можете использовать.Обратите внимание, что эти шаблоны проектирования на самом деле не помогут в других случаях использования асинхронного кода.
1.Функция инициализации
Как вы уже продемонстрировали в своем коде, это один из способов сделать это.По сути, у вас есть асинхронный метод для инициализации вашего ресурса.Это похоже на функцию .ready()
в jQuery.Есть несколько способов написать функцию инициализации.Наиболее простым, вероятно, является принятие обратного вызова, позволяющего вам продолжить свою логику:
class Foo {
init (callback) {
connectToDB().then(db => {
this.db = db;
callback(this);
});
}
}
использование:
let foo = new Foo();
foo.init(async function(){
await foo.save();
});
2.Шаблон Builder
Этот шаблон проектирования более распространен в мире Java и реже встречается в javascript.Шаблон строителя используется, когда ваш объект нуждается в сложной инициализации.Потребность в асинхронном ресурсе - это как раз та сложность, которая хорошо подходит для шаблона компоновщика:
class Foo {
constructor (db) {
if (typeof db === 'undefined') {
throw new Error('Cannot be called directly');
}
this.db = db;
}
static async build () {
let db = await connectToDB();
return new Foo(db);
}
}
использование:
Foo.build().then(foo => {
foo.save();
});
3.Инициализация по требованию / скрытая инициализация
Этот шаблон проектирования полезен, если ваша инициализация запутана или сложна, и вы предпочитаете более чистый API.Идея состоит в том, чтобы кэшировать ресурс и инициализировать его, только когда он еще не инициализирован:
class Foo {
constructor () {
this.db = null;
}
db () {
if (this._dbConnection !== null) {
return Promise.resolve(this._dbConnection);
}
else {
return connectToDB().then(db => {
this._dbConnection = db;
return db;
})
}
}
async save (data) {
let db = await this.db();
return db.saveData(data);
}
}
использование:
async function () {
let foo = new Foo();
await foo.save(something); // no init!!
await foo.save(somethingElse);
}
Бонус
Если вы оглянетесь назадна примере функции init вы увидите, что обратный вызов выглядит как структура управления - вроде while()
или if()
.Это одна из главных особенностей анонимных функций - возможность создавать управляющие структуры.Хорошие примеры этого есть в стандартном javascript, таком как .map()
и .forEach()
и даже в старом добром .sort()
.
Вы можете создавать асинхронные управляющие структуры (coalan / async и async-qбиблиотеки являются хорошими примерами этого).Вместо:
if( ! (await this.tableExists()) ) { ...
Вы можете записать это как:
this.ifTableNotExist(()=>{
return this.createTable();
})
.then(()=>{ ...
возможная реализация:
ifTableNotExist (callback) {
return new Promise((ok,err) => {
someAsyncFunction((table) => {
if (!table) ok(callback());
});
});
}
async / await - это всего лишь один инструмент в асинхронном программировании.И сам по себе шаблон дизайна.Поэтому ограничение себя асинхронным / ожидающим ограничивает ваш дизайн программного обеспечения.Познакомьтесь с анонимными функциями, и вы увидите множество возможностей для рефакторинга асинхронного кода.
Бонус за 2-й
В примере для шаблона инициализации по требованию пример использования сохраняет две части данныхпоследовательно с помощью await.Это произошло потому, что код дважды инициализировал соединение с БД, если мы не дожидаемся его завершения.
Но что, если мы захотим ускорить код и выполнить оба сохранения параллельно?Что если мы хотим сделать это:
// Parallel:
await Promise.all([
foo.save(something),
foo.save(somethingElse)
]);
Что мы можем сделать, так это то, что мы можем проверить метод .db()
, если есть ожидающее обещание:
// method to get db connection:
db () {
if (this._dbConnection !== null) {
return Promise.resolve(this._dbConnection);
}
else {
if (this._dbPromise === null) {
this._dbPromise = connectToDB().then(db => {
this._dbConnection = db;
return db;
})
}
return this._dbPromise;
}
}
Фактически, так какнет никаких ограничений на то, сколько раз мы можем позвонить .then()
на обещании, мы можем упростить это и просто кэшировать обещание (не знаю, почему я раньше не думал об этом):
// method to get db connection:
db () {
if (this._dbPromise === null) {
this._dbPromise = connectToDB();
}
return this._dbPromise;
}