Selesaikan Javascript Promise di luar lingkup fungsi


280

Saya telah menggunakan ES6 Promise.

Biasanya, Janji dibuat dan digunakan seperti ini

new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

Tetapi saya telah melakukan sesuatu seperti di bawah ini untuk mengambil tekad di luar demi fleksibilitas.

var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) { 
    outsideResolve = resolve; 
    outsideReject = reject; 
});

Dan kemudian

onClick = function(){
    outsideResolve();
}

Ini berfungsi dengan baik, tetapi apakah ada cara yang lebih mudah untuk melakukan ini? Jika tidak, apakah ini praktik yang baik?


2
Saya tidak berpikir ada cara lain. Saya yakin sudah ditentukan bahwa callback yang diteruskan Promiseharus dijalankan secara serempak untuk memungkinkan "mengekspor" dua fungsi.
Felix Kling

1
Ini bekerja untuk saya persis seperti yang Anda tulis. Jadi sejauh yang saya ketahui, ini adalah cara "kanonik".
Gilad Barner

14
Saya pikir harus ada cara formal untuk mencapai ini di masa depan. Menurut saya, fitur ini sangat kuat karena Anda bisa menunggu nilai dari konteks lain.
Jose

Setiap kali mereka menemukan solusi yang tepat untuk masalah ini, saya berharap mereka juga akan membuatnya bekerja untuk janji-janji bersarang, beberapa di antaranya mungkin muncul kembali.
Arthur Tarasov

Saya pikir API Janji "menyarankan" untuk selalu menggunakannya sebagai nilai pengembalian dan tidak pernah sebagai objek yang dapat Anda akses atau panggil. Dengan kata lain memaksa kita untuk memperlakukan mereka sebagai nilai pengembalian alih-alih objek yang dapat kita akses atau fungsi yang dapat kita panggil atau sesuatu yang dapat kita rujuk dengan variabel atau lulus sebagai parameter, dll. Jika Anda mulai menggunakan janji sebagai objek lain mungkin Anda akan akhirnya perlu menyelesaikannya dari luar seperti dalam pertanyaan Anda ... Yang sedang berkata, saya juga berpikir harus ada cara formal untuk melakukan ini ... dan Ditunda tampaknya hanya solusi bagi saya.
cancerbero

Jawaban:


93

Tidak, tidak ada cara lain untuk melakukan ini - satu-satunya hal yang dapat saya katakan adalah bahwa use case ini tidak terlalu umum. Seperti yang dikatakan Felix dalam komentar - apa yang Anda lakukan akan konsisten bekerja.

Perlu disebutkan bahwa alasan konstruktor janji berperilaku seperti ini adalah keselamatan lemparan - jika pengecualian yang tidak Anda antisipasi terjadi saat kode Anda berjalan di dalam konstruktor janji, maka akan berubah menjadi penolakan, bentuk keselamatan lemparan ini - mengubah kesalahan yang dilemparkan ke penolakan adalah penting dan membantu mempertahankan kode yang dapat diprediksi.

Untuk alasan keamanan lemparan ini, konstruktor janji dipilih lebih dari yang ditangguhkan (yang merupakan cara konstruksi janji alternatif yang memungkinkan apa yang Anda lakukan) - seperti untuk praktik terbaik - saya akan meneruskan elemen dan menggunakan konstruktor janji sebagai gantinya:

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));

Untuk alasan ini - kapan pun Anda dapat menggunakan konstruktor janji daripada mengekspor fungsi - Saya sarankan Anda menggunakannya Setiap kali Anda dapat menghindari keduanya - hindari keduanya dan rantai.

Perhatikan, bahwa Anda tidak boleh menggunakan konstruktor janji untuk hal-hal seperti if(condition), contoh pertama dapat ditulis sebagai:

var p = Promise[(someCondition)?"resolve":"reject"]();

2
Hai Benjamin! Apakah saat ini tidak ada cara yang lebih baik untuk mendapatkan gula yummy jika kita tidak tahu kapan janji itu akan dipenuhi? Seperti semacam pola tunggu / pemberitahuan asinkron ? Seperti misalnya, "toko", dan kemudian meminta Promiserantai? Misalnya dalam kasus khusus saya, saya berada di server, menunggu balasan klien tertentu (goyangan tangan SYN-ACK-agak untuk memastikan klien berhasil memperbarui keadaan).
Domi

1
@Domi memeriksa q-koneksi dan RxJS.
Benjamin Gruenbaum

2
Bagaimana saya bisa melakukan hal yang sama menggunakan fetch API?
Vinod Sobale

96
Tidak umum? Saya akhirnya membutuhkannya hampir di setiap proyek.
Tomáš Zato - Reinstate Monica

1
Adapun usecase menganggap Anda perlu melakukan sesuatu setelah suatu peristiwa dipicu dan sesuatu yang lain terjadi. Anda ingin mengubah acara menjadi janji dan menyatukannya dengan janji lain. Sepertinya masalah umum bagi saya.
Gherman

130

sederhana:

var promiseResolve, promiseReject;

var promise = new Promise(function(resolve, reject){
  promiseResolve = resolve;
  promiseReject = reject;
});

promiseResolve();

2
@ruX, Seperti jawaban yang diterima menyebutkan - itu dirancang seperti ini dengan sengaja. Intinya adalah bahwa jika sebuah pengecualian dilemparkan, ia akan ditangkap oleh konstruktor janji. Jawaban ini (juga milik saya) memiliki perangkap untuk kemungkinan melemparkan pengecualian untuk panggilan kode apa pun promiseResolve(). Semantik dari sebuah janji adalah bahwa ia selalu mengembalikan nilai. Juga ini secara fungsional sama dengan posting OP, saya tidak mendapatkan masalah apa yang diselesaikan dengan cara yang dapat digunakan kembali.
Jon Jaques

4
@JonJaques Saya tidak yakin apa yang Anda katakan itu benar. Kode yang memanggil promiseResolve()tidak akan memunculkan eksepsi. Anda dapat mendefinisikan .catchpada konstruktor dan tidak peduli apa kode menyebutnya, konstruktor .catchakan dipanggil. Inilah jsbin yang menunjukkan cara kerjanya: jsbin.com/yicerewivo/edit?js,console
carter

Ya, itu tertangkap karena Anda membungkus konstruktor janji lain di sekitarnya - Persis yang ingin saya sampaikan. Namun, katakanlah Anda memiliki beberapa kode lain yang mencoba memanggil resolusikan () di luar konstruktor (alias objek Ditangguhkan) ... Itu bisa melempar pengecualian dan tidak ditangkap jsbin.com/cokiqiwapo/1/edit?js,console
Jon Jaques 6-16

8
Saya bahkan tidak yakin itu desain yang buruk. Kesalahan yang dilontarkan di luar janji tidak seharusnya terjebak dalam janji. Ini mungkin merupakan contoh kesalahpahaman atau pemahaman yang buruk, jika desainer benar-benar mengharapkan kesalahan untuk ditangkap.
KalEl

3
Konstruk yang tepat ini sudah disebutkan dalam pertanyaan. Apakah Anda bahkan membacanya?
Cedric Reichenbach

103

Agak terlambat ke pesta di sini, tetapi cara lain untuk melakukannya adalah dengan menggunakan objek Ditangguhkan . Pada dasarnya Anda memiliki jumlah pelat yang sama, tetapi sangat berguna jika Anda ingin membagikannya dan mungkin menyelesaikannya di luar definisi mereka.

Implementasi Naif:

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject)=> {
      this.reject = reject
      this.resolve = resolve
    })
  }
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(()=> {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(result => {
  console.log(result) // 42
})

Versi ES5:

function Deferred() {
  var self = this;
  this.promise = new Promise(function(resolve, reject) {
    self.reject = reject
    self.resolve = resolve
  })
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(function() {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(function(result) {
  console.log(result) // 42
})

1
Perhatikan cakupan leksikal di sini.
Florrie

1
Tidak ada perbedaan praktis dalam apakah resolve|rejectditugaskan secara leksikal atau melalui bind. Ini hanyalah implementasi sederhana dari objek jQuery Deferred yang telah ada sejak 1.0 (ish). Ia bekerja persis seperti janji, kecuali tidak ada keamanan lemparan. Inti dari pertanyaan ini adalah bagaimana cara menyimpan beberapa baris kode saat membuat janji.
Jon Jaques

1
Menggunakan tangguhan adalah cara biasa untuk melakukan ini, saya tidak tahu mengapa ini tidak lebih tinggi
BlueRaja - Danny Pflughoeft

1
Jawaban yang sangat bagus! Sedang mencari fungsionalitas yang ditangguhkan yang ditawarkan jQuery.
Anshul Koka

2
Apakah Deferredsudah usang?
Pacerier

19

Solusi yang saya buat pada 2015 untuk kerangka kerja saya. Saya menyebut jenis janji ini Task

function createPromise(handler){
  var _resolve, _reject;

  var promise = new Promise(function(resolve, reject){
    _resolve = resolve; 
    _reject = reject;

    handler(resolve, reject);
  })

  promise.resolve = _resolve;
  promise.reject = _reject;

  return promise;
}

var promise = createPromise()
promise.then(function(data){ alert(data) })

promise.resolve(200) // resolve from outside

4
Terima kasih, ini berhasil. Tapi apa itu handler? Saya harus menghapusnya agar berfungsi.
Sahid

16

Saya suka jawaban @JonJaques tapi saya ingin melangkah lebih jauh.

Jika Anda mengikat thendan catchkemudian Deferredobjek, maka sepenuhnya mengimplementasikan PromiseAPI dan Anda dapat memperlakukannya sebagai janji dan awaititu dan semacamnya.

class DeferredPromise {
  constructor() {
    this._promise = new Promise((resolve, reject) => {
      // assign the resolve and reject functions to `this`
      // making them usable on the class instance
      this.resolve = resolve;
      this.reject = reject;
    });
    // bind `then` and `catch` to implement the same interface as Promise
    this.then = this._promise.then.bind(this._promise);
    this.catch = this._promise.catch.bind(this._promise);
    this[Symbol.toStringTag] = 'Promise';
  }
}

const deferred = new DeferredPromise();
console.log('waiting 2 seconds...');
setTimeout(() => {
  deferred.resolve('whoa!');
}, 2000);

async function someAsyncFunction() {
  const value = await deferred;
  console.log(value);
}

someAsyncFunction();


10

Metode pembantu akan meringankan overhead tambahan ini, dan memberi Anda perasaan jQuery yang sama.

function Deferred() {
    let resolve;
    let reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}

Penggunaan akan

const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
    confirm: resolve,
    cancel: reject
});
return promise;

Yang mirip dengan jQuery

const dfd = $.Deferred();
displayConfirmationDialog({
    confirm: dfd.resolve,
    cancel: dfd.reject
});
return dfd.promise();

Meskipun, dalam kasus penggunaan ini sederhana, sintaksis asli baik-baik saja

return new Promise((resolve, reject) => {
    displayConfirmationDialog({
        confirm: resolve,
        cancel: reject
    });
});

8

Saya menggunakan fungsi pembantu untuk membuat apa yang saya sebut "janji datar" -

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });

    return { promise, resolve, reject };
}

Dan saya menggunakannya seperti itu -

function doSomethingAsync() {

    // Get your promise and callbacks
    const { resolve, reject, promise } = flatPromise();

    // Do something amazing...
    setTimeout(() => {
        resolve('done!');
    }, 500);

    // Pass your promise to the world
    return promise;

}

Lihat contoh kerja lengkap -

Sunting: Saya telah membuat paket NPM yang disebut janji-datar dan kodenya juga tersedia di GitHub .


7

Anda bisa membungkus Janji dalam sebuah kelas.

class Deferred {
    constructor(handler) {
        this.promise = new Promise((resolve, reject) => {
            this.reject = reject;
            this.resolve = resolve;
            handler(resolve, reject);
        });

        this.promise.resolve = this.resolve;
        this.promise.reject = this.reject;

        return this.promise;
    }
    promise;
    resolve;
    reject;
}

// How to use.
const promise = new Deferred((resolve, reject) => {
  // Use like normal Promise.
});

promise.resolve(); // Resolve from any context.

6

Banyak jawaban di sini mirip dengan contoh terakhir dalam artikel ini . Saya menyimpan banyak Janji, resolve()dan reject()fungsi dan dapat ditugaskan ke variabel atau properti apa pun. Hasilnya, saya dapat membuat kode ini sedikit lebih ringkas:

function defer(obj) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
}

Berikut adalah contoh sederhana dari penggunaan versi ini defer()untuk menggabungkan FontFaceJanji beban dengan proses async lain:

function onDOMContentLoaded(evt) {
    let all = []; // array of Promises
    glob = {};    // global object used elsewhere
    defer(glob);
    all.push(glob.promise);
    // launch async process with callback = resolveGlob()

    const myFont = new FontFace("myFont", "url(myFont.woff2)");
    document.fonts.add(myFont);
    myFont.load();
    all.push[myFont];
    Promise.all(all).then(() => { runIt(); }, (v) => { alert(v); });
}
//...
function resolveGlob() {
    glob.resolve();
}
function runIt() {} // runs after all promises resolved 

Pembaruan: 2 alternatif jika Anda ingin merangkum objek:

function defer(obj = {}) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
    return obj;
}
let deferred = defer();

dan

class Deferred {
    constructor() {
        this.promise = new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject  = reject;
        });
    }
}
let deferred = new Deferred();

Jika Anda menggunakan contoh-contoh ini dalam fungsi async, Anda harus merujuk ke properti janji, ketika Anda ingin menggunakan nilai dari janji yang diselesaikan:const result = await deferred.promise;
b00t

6

Jawaban yang diterima salah. Ini cukup mudah menggunakan ruang lingkup dan referensi, meskipun mungkin membuat Janji puritan marah:

const createPromise = () => {
    let resolver;
    return [
        new Promise((resolve, reject) => {
            resolver = resolve;
        }),
        resolver,
    ];
};

const [ promise, resolver ] = createPromise();
promise.then(value => console.log(value));
setTimeout(() => resolver('foo'), 1000);

Kami pada dasarnya mengambil referensi ke fungsi ketetapan saat janji dibuat, dan kami mengembalikannya sehingga dapat diatur secara eksternal.

Dalam satu detik konsol akan menampilkan:

> foo

Saya pikir ini adalah pendekatan terbaik. Satu-satunya hal adalah bahwa kode bisa sedikit kurang bertele-tele.
pie6k

Bagus! Ide pintar +50 jika aku bisa.
Mitya

Inilah yang OP lakukan. Sebenarnya Anda sedang menciptakan kembali Pola tangguhan atas Janji, tentu saja ini mungkin dan pendekatan Anda berfungsi (seperti kode OP awal), tetapi ini bukan praktik terbaik karena "alasan keamanan melemparkan" yang dijelaskan dalam jawaban yang diterima.
dhilt

4

Ya kamu bisa. Dengan menggunakan CustomEventAPI untuk lingkungan browser. Dan menggunakan proyek acara emitor di lingkungan node.js. Karena cuplikan dalam pertanyaan adalah untuk lingkungan peramban, berikut adalah contoh yang berfungsi untuk hal yang sama.

function myPromiseReturningFunction(){
  return new Promise(resolve => {
    window.addEventListener("myCustomEvent", (event) => {
       resolve(event.detail);
    }) 
  })
}


myPromiseReturningFunction().then(result => {
   alert(result)
})

document.getElementById("p").addEventListener("click", () => {
   window.dispatchEvent(new CustomEvent("myCustomEvent", {detail : "It works!"}))
})
<p id="p"> Click me </p>

Semoga jawaban ini bermanfaat!


3

Solusi kami adalah menggunakan penutupan untuk menyimpan fungsi tekad / tolak dan juga lampirkan fungsi untuk memperpanjang janji itu sendiri.

Berikut polanya:

function getPromise() {

    var _resolve, _reject;

    var promise = new Promise((resolve, reject) => {
        _reject = reject;
        _resolve = resolve;
    });

    promise.resolve_ex = (value) => {
       _resolve(value);
    };

    promise.reject_ex = (value) => {
       _reject(value);
    };

    return promise;
}

Dan menggunakannya:

var promise = getPromise();

promise.then(value => {
    console.info('The promise has been fulfilled: ' + value);
});

promise.resolve_ex('hello');  
// or the reject version 
//promise.reject_ex('goodbye');

2
Hebat ... Saya baru belajar Janji tetapi secara konsisten bingung oleh kenyataan bahwa Anda tampaknya tidak dapat menyelesaikannya "di tempat lain". Menggunakan penutupan untuk menyembunyikan detail implementasi adalah ide yang bagus ... tapi sebenarnya saya tidak yakin itu yang Anda lakukan: daripada memiliki variabel pribadi "semu", saya cukup yakin ada cara untuk sepenuhnya menyembunyikan variabel yang seharusnya tidak dapat diakses ... yang sebenarnya merupakan arti penutupan ...
mike rodent

> Penutupan adalah blok kode yang dapat direferensikan (dan diedarkan) dengan akses ke variabel lingkup melampirkan. var _resolve, _reject; adalah ruang lingkup terlampir.
Steven Spungin

ya, cukup adil. Sebenarnya menurut saya jawaban saya terlalu rumit, dan lebih jauh lagi jawaban Anda dapat disederhanakan: Anda hanya perlu pergi promise.resolve_ex = _resolve; promise.reject_ex = _reject;... masih berfungsi dengan baik.
mike rodent

" lampirkan fungsi untuk memperpanjang janji itu sendiri. " - jangan lakukan itu. Janji adalah nilai hasil, mereka seharusnya tidak memberikan kemampuan untuk menyelesaikannya. Anda tidak ingin melewati yang diperpanjang itu.
Bergi

2
Pertanyaannya adalah bagaimana menyelesaikannya di luar ruang lingkup. Inilah solusi yang berfungsi, dan dalam produksi kami, kami sebenarnya memiliki alasan yang diperlukan untuk melakukannya. Saya tidak melihat mengapa menyelesaikan masalah yang disebutkan layak downvote.
Steven Spungin

2

Saya menemukan diri saya kehilangan pola ditangguhkan juga dalam kasus-kasus tertentu. Anda selalu dapat membuat satu di atas Janji ES6:

export default class Deferred<T> {
    private _resolve: (value: T) => void = () => {};
    private _reject: (value: T) => void = () => {};

    private _promise: Promise<T> = new Promise<T>((resolve, reject) => {
        this._reject = reject;
        this._resolve = resolve;
    })

    public get promise(): Promise<T> {
        return this._promise;
    }

    public resolve(value: T) {
        this._resolve(value);
    }

    public reject(value: T) {
        this._reject(value);
    }
}

2

Terima kasih kepada semua orang yang memposting di utas ini. Saya membuat modul yang menyertakan objek Defer () yang dijelaskan sebelumnya serta beberapa objek lain yang dibangun di atasnya. Mereka semua memanfaatkan Janji dan sintaksis Panggilan balik yang rapi untuk mengimplementasikan komunikasi / penanganan acara dalam suatu program.

  • Tunda: Janji yang bisa diselesaikan gagal dari jarak jauh (di luar tubuhnya)
  • Penundaan: Janji yang diselesaikan secara otomatis setelah waktu tertentu
  • TimeOut: Janji yang gagal secara otomatis setelah waktu tertentu.
  • Siklus: Janji yang dapat dipicu kembali untuk mengelola acara dengan sintaksis Janji
  • Antrian: Antrian eksekusi berdasarkan Chaining janji.

    rp = require("repeatable-promise")

    https://github.com/CABrouwers/repeatable-promise


1

Saya menulis lib kecil untuk ini. https://www.npmjs.com/package/@inf3rno/promise.exposed

Saya menggunakan pendekatan metode pabrik lain menulis, tapi saya mengesampingkan then, catch, finallymetode juga, sehingga Anda dapat menyelesaikan janji asli oleh mereka juga.

Menyelesaikan Janji tanpa pelaksana dari luar:

const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");

Balap dengan setTimeout pelaksana dari luar:

const promise = Promise.exposed(function (resolve, reject){
    setTimeout(function (){
        resolve("I almost fell asleep.")
    }, 100000);
}).then(console.log);

setTimeout(function (){
    promise.resolve("I don't want to wait that much.");
}, 100);

Ada mode tanpa konflik jika Anda tidak ingin mencemari namespace global:

const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");

1

Saya membuat perpustakaan bernama manual-promiseyang berfungsi sebagai pengganti Promise. Tidak ada jawaban lain di sini yang akan berfungsi sebagai pengganti pengganti Promise, karena mereka menggunakan proksi atau pembungkus.

yarn add manual-promise

npn install manual-promise


import { ManualPromise } from "manual-promise";

const prom = new ManualPromise();

prom.resolve(2);

// actions can still be run inside the promise
const prom2 = new ManualPromise((resolve, reject) => {
    // ... code
});


new ManualPromise() instanceof Promise === true

https://github.com/zpxp/manual-promise#readme


0

Bagaimana dengan membuat fungsi untuk membajak tolak dan mengembalikannya?

function createRejectablePromise(handler) {
  let _reject;

  const promise = new Promise((resolve, reject) => {
    _reject = reject;

    handler(resolve, reject);
  })

  promise.reject = _reject;
  return promise;
}

// Usage
const { reject } = createRejectablePromise((resolve) => {
  setTimeout(() => {
    console.log('resolved')
    resolve();
  }, 2000)

});

reject();

0

Saya telah mengumpulkan inti yang melakukan pekerjaan itu: https://gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13

inilah cara Anda harus menggunakannya:

import ExternalizedPromiseCreator from '../externalized-promise';

describe('ExternalizedPromise', () => {
  let fn: jest.Mock;
  let deferredFn: jest.Mock;
  let neverCalledFn: jest.Mock;
  beforeEach(() => {
    fn = jest.fn();
    deferredFn = jest.fn();
    neverCalledFn = jest.fn();
  });

  it('resolve should resolve the promise', done => {
    const externalizedPromise = ExternalizedPromiseCreator.create(() => fn());

    externalizedPromise
      .promise
      .then(() => deferredFn())
      .catch(() => neverCalledFn())
      .then(() => {
        expect(deferredFn).toHaveBeenCalled();
        expect(neverCalledFn).not.toHaveBeenCalled();
        done();
      });

    expect(fn).toHaveBeenCalled();
    expect(neverCalledFn).not.toHaveBeenCalled();
    expect(deferredFn).not.toHaveBeenCalled();

    externalizedPromise.resolve();
  });
  ...
});

0

pertama-tama aktifkan --allow-natives-syntax di browser atau node

const p = new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

onClick = function () {
    %ResolvePromise(p, value)
}

0

Hanya solusi lain untuk menyelesaikan Janji dari luar

 class Lock {
        #lock;  // Promise to be resolved (on  release)
        release;  // Release lock
        id;  // Id of lock
        constructor(id) {
            this.id = id
            this.#lock = new Promise((resolve) => {
                this.release = () => {
                    if (resolve) {
                        resolve()
                    } else {
                        Promise.resolve()
                    }
                }
            })
        }
        get() { return this.#lock }
    }

Pemakaian

let lock = new Lock(... some id ...);
...
lock.get().then(()=>{console.log('resolved/released')})
lock.release()  // Excpected 'resolved/released'
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.