Cách thực hiện lời hứa từ setTimeout


91

Đây không phải là vấn đề của thế giới thực, tôi chỉ đang cố gắng hiểu cách tạo ra những lời hứa.

Tôi cần hiểu cách thực hiện một lời hứa cho một hàm không trả về gì, như setTimeout.

Giả sử tôi có:

function async(callback){ 
    setTimeout(function(){
        callback();
    }, 5000);
}

async(function(){
    console.log('async called back');
});

Làm cách nào để tạo một lời hứa asynccó thể quay trở lại sau khi setTimeoutsẵn sàng callback()?

Tôi cho rằng gói nó sẽ đưa tôi đi đâu đó:

function setTimeoutReturnPromise(){

    function promise(){}

    promise.prototype.then = function() {
        console.log('timed out');
    };

    setTimeout(function(){
        return ???
    },2000);


    return promise;
}

Nhưng tôi không thể nghĩ xa hơn điều này.


Bạn đang cố gắng tạo thư viện lời hứa của riêng mình ?
TJ Crowder

@TJCrowder Tôi thì không nhưng tôi đoán bây giờ đó thực sự là những gì tôi đang cố gắng hiểu. Đó là cách một thư viện sẽ làm điều đó
laggingreflex

@ lagging: Có lý, tôi đã thêm một ví dụ về triển khai lời hứa cơ bản vào câu trả lời.
TJ Crowder

Tôi nghĩ đây là một vấn đề rất thực tế và một vấn đề mà tôi phải giải quyết cho một dự án lớn mà công ty tôi đang xây dựng. Có thể có nhiều cách tốt hơn để làm điều đó, nhưng về cơ bản tôi cần trì hoãn giải pháp của một lời hứa vì lợi ích của ngăn xếp bluetooth của chúng tôi. Tôi sẽ đăng dưới đây để cho thấy những gì tôi đã làm.
sunny-mittal

1
Chỉ cần một lưu ý rằng vào năm 2017 'async' là một cái tên hơi khó hiểu đối với một chức năng, như bạn có thể cóasync function async(){...}
mikemaccana

Câu trả lời:


121

Cập nhật (2017)

Ở đây vào năm 2017, Promises được tích hợp vào JavaScript, chúng được thêm vào bởi thông số kỹ thuật ES2015 (polyfills có sẵn cho các môi trường lỗi thời như IE8-IE11). Cú pháp mà chúng đi cùng sử dụng một lệnh gọi lại mà bạn truyền vào hàm Promisetạo (trình Promise thực thi ), hàm này nhận các hàm để giải quyết / từ chối lời hứa dưới dạng đối số.

Đầu tiên, vì asyncbây giờ có một ý nghĩa trong JavaScript (mặc dù nó chỉ là một từ khóa trong một số ngữ cảnh nhất định), tôi sẽ sử dụng laterlàm tên của hàm để tránh nhầm lẫn.

Độ trễ cơ bản

Sử dụng các hứa hẹn gốc (hoặc một polyfill trung thực), nó sẽ giống như sau:

function later(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}

Lưu ý rằng, cho rằng một phiên bản của setTimeoutđó là phù hợp với định nghĩa cho các trình duyệtsetTimeoutkhông vượt qua bất kỳ đối số để gọi lại trừ khi bạn cung cấp cho họ sau khi khoảng thời gian (điều này có thể không đúng trong môi trường phi trình duyệt, và không sử dụng được đúng trên Firefox, nhưng bây giờ là đúng; nó đúng trên Chrome và thậm chí là trên IE8).

Độ trễ cơ bản có giá trị

Nếu bạn muốn chức năng của mình tùy chọn chuyển một giá trị độ phân giải, trên bất kỳ trình duyệt mơ hồ hiện đại nào cho phép bạn cung cấp thêm các đối số setTimeoutsau khoảng thời gian trì hoãn và sau đó chuyển các đối số đó đến lệnh gọi lại khi được gọi, bạn có thể làm điều này (Firefox và Chrome hiện tại; IE11 + , có lẽ là Edge; không phải IE8 hoặc IE9, không có ý tưởng về IE10):

function later(delay, value) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay, value); // Note the order, `delay` before `value`
        /* Or for outdated browsers that don't support doing that:
        setTimeout(function() {
            resolve(value);
        }, delay);
        Or alternately:
        setTimeout(resolve.bind(null, value), delay);
        */
    });
}

Nếu bạn đang sử dụng ES2015 + các hàm mũi tên, có thể ngắn gọn hơn:

function later(delay, value) {
    return new Promise(resolve => setTimeout(resolve, delay, value));
}

hoặc thậm chí

const later = (delay, value) =>
    new Promise(resolve => setTimeout(resolve, delay, value));

Trì hoãn có thể hủy bỏ với giá trị

Nếu bạn muốn có thể hủy thời gian chờ, bạn không thể chỉ trả lại lời hứa từ đó later, vì không thể hủy bỏ lời hứa.

Nhưng chúng ta có thể dễ dàng trả về một đối tượng với một cancelphương thức và một trình truy cập cho lời hứa và từ chối lời hứa khi hủy bỏ:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

Ví dụ trực tiếp:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

const l1 = later(100, "l1");
l1.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l1 cancelled"); });

const l2 = later(200, "l2");
l2.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l2 cancelled"); });
setTimeout(() => {
  l2.cancel();
}, 150);


Câu trả lời gốc từ năm 2014

Thông thường, bạn sẽ có một thư viện lời hứa (một thư do bạn tự viết, hoặc một trong một số thư viện có sẵn). Thư viện đó thường sẽ có một đối tượng mà bạn có thể tạo và sau đó "giải quyết", và đối tượng đó sẽ có một "lời hứa" mà bạn có thể nhận được từ nó.

Sau đó latersẽ có xu hướng trông giống như sau:

function later() {
    var p = new PromiseThingy();
    setTimeout(function() {
        p.resolve();
    }, 2000);

    return p.promise(); // Note we're not returning `p` directly
}

Trong một bình luận về câu hỏi, tôi đã hỏi:

Bạn đang cố gắng tạo thư viện lời hứa của riêng mình?

và bạn nói

Tôi thì không nhưng tôi đoán bây giờ đó thực sự là những gì tôi đang cố gắng hiểu. Đó là cách một thư viện sẽ làm điều đó

Để hỗ trợ sự hiểu biết đó, đây là một ví dụ rất cơ bản , không tuân thủ Promises-A từ xa: Live Copy

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
  <script>
    (function() {

      // ==== Very basic promise implementation, not remotely Promises-A compliant, just a very basic example
      var PromiseThingy = (function() {

        // Internal - trigger a callback
        function triggerCallback(callback, promise) {
          try {
            callback(promise.resolvedValue);
          }
          catch (e) {
          }
        }

        // The internal promise constructor, we don't share this
        function Promise() {
          this.callbacks = [];
        }

        // Register a 'then' callback
        Promise.prototype.then = function(callback) {
          var thispromise = this;

          if (!this.resolved) {
            // Not resolved yet, remember the callback
            this.callbacks.push(callback);
          }
          else {
            // Resolved; trigger callback right away, but always async
            setTimeout(function() {
              triggerCallback(callback, thispromise);
            }, 0);
          }
          return this;
        };

        // Our public constructor for PromiseThingys
        function PromiseThingy() {
          this.p = new Promise();
        }

        // Resolve our underlying promise
        PromiseThingy.prototype.resolve = function(value) {
          var n;

          if (!this.p.resolved) {
            this.p.resolved = true;
            this.p.resolvedValue = value;
            for (n = 0; n < this.p.callbacks.length; ++n) {
              triggerCallback(this.p.callbacks[n], this.p);
            }
          }
        };

        // Get our underlying promise
        PromiseThingy.prototype.promise = function() {
          return this.p;
        };

        // Export public
        return PromiseThingy;
      })();

      // ==== Using it

      function later() {
        var p = new PromiseThingy();
        setTimeout(function() {
          p.resolve();
        }, 2000);

        return p.promise(); // Note we're not returning `p` directly
      }

      display("Start " + Date.now());
      later().then(function() {
        display("Done1 " + Date.now());
      }).then(function() {
        display("Done2 " + Date.now());
      });

      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        document.body.appendChild(p);
      }
    })();
  </script>
</body>
</html>


Câu trả lời của bạn không xử lý cancelTimeout
Alexander Danilov

@AlexanderDanilov: Không thể hủy bỏ lời hứa. Bạn chắc chắn có thể viết một hàm trả về một đối tượng với một phương pháp hủy bỏ và, riêng biệt, một accessor cho lời hứa, sau đó từ chối lời hứa nếu phương pháp hủy được gọi là ...
TJ Crowder

1
@AlexanderDanilov: Tôi đã tiếp tục và thêm một cái.
TJ Crowder

0

Đây không phải là câu trả lời cho câu hỏi ban đầu. Nhưng, vì một câu hỏi ban đầu không phải là một vấn đề trong thế giới thực nên nó không phải là một vấn đề. Tôi đã cố gắng giải thích cho một người bạn những lời hứa trong JavaScript và sự khác biệt giữa lời hứa và lệnh gọi lại.

Mã bên dưới coi như giải thích:

//very basic callback example using setTimeout
//function a is asynchronous function
//function b used as a callback
function a (callback){
    setTimeout (function(){
       console.log ('using callback:'); 
       let mockResponseData = '{"data": "something for callback"}'; 
       if (callback){
          callback (mockResponseData);
       }
    }, 2000);

} 

function b (dataJson) {
   let dataObject = JSON.parse (dataJson);
   console.log (dataObject.data);   
}

a (b);

//rewriting above code using Promise
//function c is asynchronous function
function c () {
   return new Promise(function (resolve, reject) {
     setTimeout (function(){
       console.log ('using promise:'); 
       let mockResponseData = '{"data": "something for promise"}'; 
       resolve(mockResponseData); 
    }, 2000);      
   }); 

}

c().then (b);

JsFiddle

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.