Tạo một lời hứa (ES6) mà không bắt đầu giải quyết nó


77

Sử dụng các lời hứa của ES6, làm cách nào để tạo một lời hứa mà không xác định logic để giải quyết nó? Đây là một ví dụ cơ bản (một số TypeScript):

var promises = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return promises[key];
  }
  var promise = new Promise(resolve => {
    // But I don't want to try resolving anything here :(
  });

  promises[key] = promise;
  return promise;
}

function resolveWith(key: string, value: any): void {
  promises[key].resolve(value); // Not valid :(
}

Nó dễ dàng thực hiện với các thư viện hứa hẹn khác. Ví dụ của JQuery:

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = $.Deferred();    
  deferreds[key] = def;
  return def.promise();
}

function resolveWith(key: string, value: any): void {
  deferreds[key].resolve(value);
}

Cách duy nhất tôi có thể thấy để làm điều này là lưu trữ hàm giải quyết ở đâu đó trong trình thực thi của lời hứa nhưng điều đó có vẻ lộn xộn và tôi không chắc nó được xác định khi nào chính xác hàm này được chạy - nó luôn chạy ngay lập tức khi đang xây dựng?

Cảm ơn.


1
WTH bạn sẽ làm một cái gì đó như vậy? Một lời hứa không có logic giải quyết là một lời hứa mãi mãi chờ đợi.
Bergi

Phần thứ hai của câu hỏi của bạn là một bản sao của JavaScript Promise Callback có được thực thi không đồng bộ không
Bergi

1
@Bergi - Hãy tưởng tượng một thứ giống như một hệ thống tiêm phụ thuộc không đồng bộ. Bạn có một phần nơi bạn đăng ký các vật phẩm tiêm và một phần nơi bạn yêu cầu chúng. Nếu tôi yêu cầu một mặt hàng chưa được đăng ký, thì tôi sẽ muốn trả lại một lời hứa sẽ giải quyết khi nó có.
Barguast

1
Xem thêm các Lời hứa
Bergi

Câu trả lời:


91

Câu hỏi hay!

Trình phân giải được chuyển đến phương thức khởi tạo Hứa cố tình chạy đồng bộ để hỗ trợ trường hợp sử dụng này:

var deferreds = [];
var p = new Promise(function(resolve, reject){
    deferreds.push({resolve: resolve, reject: reject});
});

Sau đó, tại một số thời điểm sau:

 deferreds[0].resolve("Hello"); // resolve the promise with "Hello"

Lý do mà hàm tạo lời hứa được đưa ra là:

  • Thông thường (nhưng không phải luôn luôn) logic phân giải được ràng buộc với việc tạo.
  • Hàm tạo hứa hẹn là ném an toàn và chuyển đổi các ngoại lệ thành từ chối.

Đôi khi nó không phù hợp và trình phân giải chạy đồng bộ. Đây là bài đọc liên quan về chủ đề này .


Tôi đã suy nghĩ dọc theo dòng Promise.resolvevà lưu trữ các ràng buộc then. Tuy nhiên, điều này trông sạch sẽ và tôi thậm chí không chắc chắn liệu ràng buộc thencó hoạt động hay không.
thefourtheye

Chà. Cảm ơn vì câu trả lời cực kỳ nhanh chóng và thấu đáo! Bạn có tình cờ có nguồn nói rằng trình thực thi (chức năng với các đối số giải quyết và bác bỏ) luôn chạy đồng bộ tại thời điểm xây dựng không?
Barguast

1
@Barguast chắc chắn, đối với ES6 nói riêng - ecma-international.org/ecma-262/6.0/… là cách các lời hứa được xây dựng, gọi là ecma-international.org/ecma-262/6.0/index.html#sec-construct là được gọi là đồng bộ. Nó lần lượt được gọi đồng bộ từ ecma-international.org/ecma-262/6.0/… - Tôi nghĩ github.com/promises-aplus/constructor-spec cũ là một nguồn tốt hơn.
Benjamin Gruenbaum

2
@BenjaminGruenbaum: Chỉ cần điểm đến là JavaScript Promise Callback thực hiện Asynchronosuly :-)
Bergi

1
@EugeneHoza nó đã từng là (cách đây khá lâu) nhưng nó đã được thay đổi thành mẫu phương thức khởi tạo tiết lộ (được coi là an toàn hơn). Tôi đã từng thích nó - nhưng nhìn lại tôi không chắc nó thực sự an toàn hơn - đó chỉ là một sự đánh đổi. Bạn có thể đọc về nó ở đây: blog.domenic.me/the-revealing-constructor-pattern - ngoài ra nếu bạn cảm thấy say mê về nó (tất nhiên là sau khi bạn đã đọc phần thông tin cơ bản liên quan) - bạn rất hoan nghênh việc đề xuất với TC39 để giới thiệu một API khác.
Benjamin Gruenbaum

53

Tôi muốn thêm 2 xu của mình vào đây. Xem xét chính xác câu hỏi " Tạo một Hứa hẹn es6 mà không cần bắt đầu giải quyết nó ", tôi đã giải quyết nó bằng cách tạo một hàm wrapper và gọi hàm wrapper để thay thế. Mã:

Giả sử chúng ta có một hàm ftrả về Promise

/** @return Promise<any> */
function f(args) {
   return new Promise(....)
}

// calling f()
f('hello', 42).then((response) => { ... })

Bây giờ, tôi muốn chuẩn bị một cuộc gọi đến f('hello', 42)mà không thực sự giải quyết nó:

const task = () => f('hello', 42) // not calling it actually

// later
task().then((response) => { ... })

Hy vọng điều này sẽ giúp ai đó :)


Tham khảo Promise.all()như được hỏi trong phần nhận xét (và được trả lời bởi @Joe Frambach), nếu tôi muốn chuẩn bị cuộc gọi đến f1('super')& f2('rainbow'), 2 hàm trả về lời hứa

const f1 = args => new Promise( ... )
const f2 = args => new Promise( ... )

const tasks = [
  () => f1('super'),
  () => f2('rainbow')
]

// later
Promise.all(tasks.map(t => t()))
  .then(resolvedValues => { ... })

Làm thế nào điều này có thể được sử dụng với Promise.all()?
Frondor

nhiệm vụ = [() => f (1), () => f (2)]; Promise.all (task.map (t => t ())).
Then

3

Làm thế nào về một cách tiếp cận toàn diện hơn?

Bạn có thể viết một Constructor trả về một Promise mới được trang trí bằng .resolve().reject()các phương thức.

Bạn có thể sẽ chọn đặt tên cho hàm tạo Deferred- một thuật ngữ có nhiều ưu tiên trong [lịch sử] các hứa hẹn javascript.

function Deferred(fn) {
    fn = fn || function(){};

    var resolve_, reject_;

    var promise = new Promise(function(resolve, reject) {
        resolve_ = resolve;
        reject_ = reject;
        fn(resolve, reject);
    });

    promise.resolve = function(val) {
        (val === undefined) ? resolve_() : resolve_(val);
        return promise;//for chainability
    }
    promise.reject = function(reason) {
        (reason === undefined) ? reject_() : reject_(reason);
        return promise;//for chainability
    }
    promise.promise = function() {
        return promise.then(); //to derive an undecorated promise (expensive but simple).
    }

    return promise;
}

Bằng cách trả về một promsie được trang trí thay vì một đối tượng đơn giản, tất cả các phương thức / thuộc tính tự nhiên của lời hứa vẫn có sẵn ngoài các trang trí.

Ngoài ra, bằng cách xử lý fn, mẫu trình tiết lộ vẫn có sẵn, nếu bạn cần / chọn sử dụng nó trong chế độ Hoãn.

BẢN GIỚI THIỆU

Bây giờ, với Deferred()tiện ích tại chỗ, mã của bạn hầu như giống với ví dụ jQuery.

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = Deferred();    
  deferreds[key] = def;
  return def.promise();
}

1
Tôi không biết, điều này dường như giới thiệu lại tất cả những điều mà chúng tôi muốn tránh khi chuyển từ hoãn thành hứa và cách tiếp cận phương thức khởi tạo. Ngoài ra, việc triển khai của bạn hoàn toàn quá phức tạp và có một số lỗi: a) nếu bạn cung cấp Deferredmột tham số gọi lại, nó sẽ được gọi lại với tham số bị hoãn b) kiểm tra value/ reasonfor undefinedlà hoàn toàn không cần thiết c) .resolve.rejectkhông bao giờ cần phải được xâu chuỗi d) bạn .promisephương pháp không trả lại một lời hứa undecorated
Bergi

Tôi đã luôn không thoải mái về việc thiết kế ra các bản hoãn. Tôi chắc chắn rằng chúng rất hiếm nhưng phải có các trường hợp sử dụng khi Trì hoãn là cần thiết - tức là khi không thể xác định phương tiện giải quyết tại thời điểm Lời hứa được tạo. Tôi không hiểu (a), tôi sẵn sàng thuyết phục về (b) và bạn chắc chắn đúng về (c).
Roamer-1888

Ý tôi là bạn nên làm fn(promise)(hay đúng hơn fn(deferred)là) thay vì fn(resolve, reject). Có, tôi chắc chắn có thể thấy nhu cầu về "các đối tượng trình giải quyết" có thể được lưu trữ ở đâu đó .fulfill.rejectcác phương thức và phương thức hiển thị , nhưng tôi nghĩ rằng chúng không nên triển khai giao diện hứa hẹn.
Bergi

@Bergi, tôi hiểu rằng jQuery, ví dụ, tiết lộ bản thân Deferred như một đối số gọi lại. Có thể tôi đang thiếu một cái gì đó nhưng tôi không thể hiểu tại sao triển khai Trì hoãn không nên bắt chước thông lệ thực tế new Promise(fn)là tiết lộ chỉ resolvereject.
Roamer-1888

Ngoài ra, trong khi when.jstài liệu không khuyến khích sử dụng nó when.defer, nó thừa nhận điều đó in certain (rare) scenarios it can be convenient to have access to both the promise and it's associated resolving functions. Nếu tuyên bố đó được tin tưởng, thì những gì tôi đưa ra ở trên sẽ có vẻ hợp lý (trong các trường hợp hiếm hoi).
Roamer-1888

1

Mọi thứ đang dần trở nên tốt hơn trong vùng đất của JavaScript, nhưng đây là một trường hợp mà mọi thứ vẫn phức tạp một cách không cần thiết. Đây là một trình trợ giúp đơn giản để hiển thị các chức năng giải quyết và từ chối:

Promise.unwrapped = () => {
  let resolve, reject, promise = new Promise((_resolve, _reject) => {
    resolve = _resolve, reject = _reject
  })
  promise.resolve = resolve, promise.reject = reject
  return promise
}

// a contrived example

let p = Promise.unwrapped()
p.then(v => alert(v))
p.resolve('test')

Rõ ràng đã từng có Promise.deferngười trợ giúp, nhưng ngay cả điều đó cũng đòi hỏi đối tượng trì hoãn phải tách biệt với chính lời hứa ...


1

Điều khiến loại vấn đề này trông phức tạp là bản chất thủ tục của Javascript. Để giải quyết vấn đề này, tôi đã tạo một lớp đơn giản. Đây là cách nó trông như thế nào:

class PendingPromise {

    constructor(args) {
        this.args = args;
    }

    execute() {
        return new Promise(this.args);
    }
}

Lời hứa này sẽ chỉ được thực hiện khi bạn gọi execute(). Ví dụ:

function log() {
    return new PendingPromise((res, rej) => {
        console.log("Hello, World!");
    });
}

log().execute();

0

CPomise cho phép bạn giải quyết các lời hứa của mình bên ngoài, nhưng đây là một phản vật chất vì nó phá vỡ mô hình đóng gói Promise. ( Bản demo trực tiếp )

import CPromise from "c-promise2";

const promise = new CPromise(() => {});

promise.then((value) => console.log(`Done: ${value}`)); //123

setTimeout(() => promise.resolve(123));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.