it-swarm.dev

Apakah praktik yang buruk untuk memiliki fungsi konstruktor mengembalikan Janji?

Saya mencoba membuat konstruktor untuk platform blogging dan memiliki banyak operasi async yang terjadi di dalamnya. Mulai dari mengambil posting dari direktori, menguraikannya, mengirimkannya melalui mesin templat, dll.

Jadi pertanyaan saya adalah, apakah tidak bijaksana jika fungsi konstruktor saya mengembalikan janji alih-alih objek dari fungsi yang mereka sebut new melawan.

Contohnya:

var engine = new Engine({path: '/path/to/posts'}).then(function (eng) {
   // allow user to interact with the newly created engine object inside 'then'
   engine.showPostsOnOnePage();
});

Sekarang, pengguna juga dapat tidak memasok link rantai Janji suplemen:

var engine = new Engine({path: '/path/to/posts'});

// ERROR
// engine will not be available as an Engine object here

Ini bisa menimbulkan masalah karena pengguna mungkin bingung mengapaenginetidak tersedia setelah konstruksi.

Alasan menggunakan Janji dalam konstruktor masuk akal. Saya ingin seluruh blog berfungsi setelah fase konstruksi. Namun, sepertinya bau hampir tidak memiliki akses ke objek segera setelah memanggil new.

Saya telah berdebat menggunakan sesuatu di sepanjang engine.start().then() atau engine.init() yang akan mengembalikan Janji itu sebagai gantinya. Tapi itu juga bau.

Sunting: Ini dalam proyek Node.js.

129
adam-beck

Ya, itu adalah praktik yang buruk. Konstruktor harus mengembalikan instance kelasnya, tidak lain. Ini akan mengacaukan operator new dan warisan sebaliknya.

Selain itu, seorang konstruktor seharusnya hanya membuat dan menginisialisasi instance baru. Seharusnya mengatur struktur data dan semua properti spesifik-instance, tetapi tidak menjalankan tugas apa pun. Ini harus berupa fungsi murni tanpa efek samping jika memungkinkan, dengan semua manfaat yang dimilikinya.

Bagaimana jika saya ingin menjalankan sesuatu dari konstruktor saya?

Itu harus masuk dalam metode kelas Anda. Anda ingin bermutasi negara global? Kemudian panggil prosedur itu secara eksplisit, bukan sebagai efek samping menghasilkan objek. Panggilan ini bisa langsung setelah instantiasi:

var engine = new Engine()
engine.displayPosts();

Jika tugas itu tidak sinkron, Anda sekarang dapat dengan mudah mengembalikan janji untuk hasilnya dari metode, untuk dengan mudah menunggu sampai selesai.
Namun saya tidak akan merekomendasikan pola ini ketika metode (asynchronous) bermutasi instance dan metode lain bergantung pada itu, karena itu akan menyebabkan mereka diminta untuk menunggu (menjadi async bahkan jika mereka sebenarnya sinkron) dan Anda d dengan cepat menjalankan manajemen antrian internal. Jangan kode instance untuk ada tetapi benar-benar tidak dapat digunakan.

Bagaimana jika saya ingin memuat data ke dalam instance saya secara tidak sinkron?

Tanyakan pada diri sendiri: Apakah Anda benar-benar membutuhkan instance tanpa data? Bisakah Anda menggunakannya entah bagaimana?

Jika jawabannya adalah Tidak, maka Anda tidak harus membuatnya sebelum Anda memiliki data. Jadikan data sebagai parameter bagi konstruktor Anda, alih-alih memberi tahu konstruktor cara mengambil data (atau memberikan janji untuk data).

Kemudian, gunakan metode statis untuk memuat data, dari mana Anda mengembalikan janji. Kemudian buat panggilan yang membungkus data dalam contoh baru tentang itu:

Engine.load({path: '/path/to/posts'}).then(function(posts) {
    new Engine(posts).displayPosts();
});

Ini memungkinkan fleksibilitas yang jauh lebih besar dalam cara memperoleh data, dan banyak menyederhanakan konstruktor. Demikian pula, Anda dapat menulis fungsi pabrik statis yang mengembalikan janji untuk contoh Engine:

Engine.fromPosts = function(options) {
    return ajax(options.path).then(Engine.parsePosts).then(function(posts) {
        return new Engine(posts, options);
    });
};

…

Engine.fromPosts({path: '/path/to/posts'}).then(function(engine) {
    engine.registerWith(framework).then(function(framePage) {
        engine.showPostsOn(framePage);
    });
});
165
Bergi

Saya mengalami masalah yang sama dan muncul dengan solusi sederhana ini.

Alih-alih mengembalikan Janji dari konstruktor, letakkan di properti this.initialization, seperti ini:

function Engine(path) {
  var engine = this
  engine.initialization = Promise.resolve()
    .then(function () {
      return doSomethingAsync(path)
    })
    .then(function (result) {
      engine.resultOfAsyncOp = result
    })
}

Kemudian, bungkus setiap metode dalam panggilan balik yang berjalan setelah inisialisasi, seperti itu:

Engine.prototype.showPostsOnPage = function () {
  return this.initialization.then(function () {
    // actual body of the method
  })
}

Bagaimana tampilannya dari perspektif konsumen API:

engine = new Engine({path: '/path/to/posts'})
engine.showPostsOnPage()

Ini berfungsi karena Anda dapat mendaftarkan beberapa panggilan balik ke sebuah janji dan mereka berjalan baik setelah itu diselesaikan atau, jika sudah diselesaikan, pada saat melampirkan kembali.

Beginilah cara mongoskin bekerja, kecuali ia tidak benar-benar menggunakan janji.


Edit: Sejak saya menulis balasan itu saya jatuh cinta dengan sintaks ES6/7 jadi ada contoh lain yang menggunakan itu. Anda dapat menggunakannya hari ini dengan babel.

class Engine {

  constructor(path) {
    this._initialized = this._initialize()
  }

  async _initialize() {
    // actual async constructor logic
  }

  async showPostsOnPage() {
    await this._initialized
    // actual body of the method
  }

}

Edit: Anda dapat menggunakan pola ini secara native dengan flag simpul 7 dan --harmony!

10
phaux

Untuk menghindari pemisahan kekhawatiran, gunakan pabrik untuk membuat objek. 

class Engine {
    constructor(data) {
        this.data = data;
    }

    static makeEngine(pathToData) {
        return new Promise((resolve, reject) => {
            getData(pathToData).then(data => {
              resolve(new Engine(data))
            }).catch(reject);
        });
    }
}
3
The Farmer

Nilai pengembalian dari konstruktor menggantikan objek yang baru diproduksi operator baru, jadi mengembalikan janji bukanlah ide yang baik. Sebelumnya, nilai pengembalian eksplisit dari konstruktor digunakan untuk pola tunggal.

Cara yang lebih baik dalam ECMAScript 2017 adalah dengan menggunakan metode statis: Anda memiliki satu proses, yaitu jumlah statis.

Metode mana yang harus dijalankan pada objek baru setelah konstruktor dapat diketahui hanya untuk kelas itu sendiri. Untuk merangkum ini di dalam kelas, Anda dapat menggunakan process.nextTick atau Promise.resolve, menunda eksekusi lebih lanjut yang memungkinkan pendengar untuk ditambahkan dan hal-hal lain di Process.launch, penyerang konstruktor.

Karena hampir semua kode dieksekusi di dalam Janji, kesalahan akan berakhir di Process.fatal

Ide dasar ini dapat dimodifikasi agar sesuai dengan kebutuhan enkapsulasi tertentu.

class MyClass {
  constructor(o) {
    if (o == null) o = false
    if (o.run) Promise.resolve()
      .then(() => this.method())
      .then(o.exit).catch(o.reject)
  }

  async method() {}
}

class Process {
  static launch(construct) {
    return new Promise(r => r(
      new construct({run: true, exit: Process.exit, reject: Process.fatal})
    )).catch(Process.fatal)
  }

  static exit() {
    process.exit()
  }

  static fatal(e) {
    console.error(e.message)
    process.exit(1)
  }
}

Process.launch(MyClass)
0
Harald Rudell