วิธีการทำสัญญาจาก setTimeout


96

นี่ไม่ใช่ปัญหาในโลกแห่งความเป็นจริงฉันแค่พยายามทำความเข้าใจว่าสัญญาถูกสร้างขึ้นมาอย่างไร

ฉันต้องเข้าใจวิธีสร้างคำมั่นสัญญาสำหรับฟังก์ชันที่ไม่ส่งคืนค่าใด ๆ เช่น setTimeout

สมมติว่าฉันมี:

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

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

ฉันจะสร้างสัญญาที่asyncสามารถกลับมาหลังจากsetTimeoutพร้อมที่จะทำcallback()อย่างไร?

ฉันคิดว่าการห่อมันจะพาฉันไปที่ไหนสักแห่ง:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

แต่ฉันไม่สามารถคิดนอกเหนือจากนี้


คุณกำลังพยายามสร้างห้องสมุดสัญญาของคุณเองหรือไม่?
TJ Crowder

@TJCrowder ฉันไม่ได้ แต่ตอนนี้ฉันคิดว่านั่นคือสิ่งที่ฉันพยายามเข้าใจ ห้องสมุดจะทำอย่างไร
laggingreflex

@ ล้าหลัง: มีเหตุผลฉันได้เพิ่มตัวอย่างการใช้คำสัญญาพื้นฐานในคำตอบ
TJ Crowder

ฉันคิดว่านี่เป็นปัญหาในโลกแห่งความเป็นจริงและเป็นปัญหาหนึ่งที่ฉันต้องแก้ไขสำหรับโครงการขนาดใหญ่ที่ บริษัท ของฉันกำลังสร้าง มีวิธีที่ดีกว่าในการทำเช่นนี้ แต่โดยพื้นฐานแล้วฉันจำเป็นต้องชะลอการแก้ปัญหาของสัญญาเพื่อประโยชน์ของบลูทู ธ สแต็กของเรา ฉันจะโพสต์ด้านล่างเพื่อแสดงสิ่งที่ฉันทำ
แดดออก

1
โปรดทราบว่าในปี 2560 'async' เป็นชื่อที่ค่อนข้างสับสนสำหรับฟังก์ชันดังที่คุณอาจมีasync function async(){...}
mikemaccana

คำตอบ:


132

อัปเดต (2017)

ที่นี่ในปี 2017 Promises ถูกสร้างขึ้นใน JavaScript ซึ่งถูกเพิ่มโดยข้อกำหนด ES2015 (โพลีฟิลล์มีให้สำหรับสภาพแวดล้อมที่ล้าสมัยเช่น IE8-IE11) ไวยากรณ์ที่ใช้ใช้การเรียกกลับที่คุณส่งผ่านไปยังตัวPromiseสร้าง (ตัวPromise ดำเนินการ ) ซึ่งรับฟังก์ชั่นสำหรับการแก้ไข / ปฏิเสธสัญญาเป็นอาร์กิวเมนต์

ประการแรกเนื่องจากasyncตอนนี้มีความหมายใน JavaScript (แม้ว่าจะเป็นเพียงคำหลักในบางบริบท) ฉันจะใช้laterเป็นชื่อของฟังก์ชันเพื่อหลีกเลี่ยงความสับสน

ความล่าช้าขั้นพื้นฐาน

การใช้คำสัญญาดั้งเดิม (หรือโพลีฟิลล์ที่ซื่อสัตย์) จะมีลักษณะดังนี้:

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

โปรดทราบว่าถือว่าเป็นเวอร์ชันsetTimeoutที่สอดคล้องกับคำจำกัดความสำหรับเบราว์เซอร์ที่setTimeoutไม่ส่งผ่านอาร์กิวเมนต์ใด ๆ ไปยังการเรียกกลับเว้นแต่คุณจะให้หลังจากช่วงเวลาดังกล่าว (ซึ่งอาจไม่เป็นจริงในสภาพแวดล้อมที่ไม่ใช่เบราว์เซอร์และไม่เคยเป็นมาก่อน จริงบน Firefox แต่ตอนนี้เป็นจริงบน Chrome และแม้กระทั่งใน IE8)

Basic Delay with Value

หากคุณต้องการให้ฟังก์ชันของคุณเป็นทางเลือกในการส่งผ่านค่าความละเอียดบนเบราว์เซอร์สมัยใหม่ที่คลุมเครือซึ่งช่วยให้คุณสามารถให้อาร์กิวเมนต์เพิ่มเติมsetTimeoutหลังจากการหน่วงเวลาแล้วส่งผ่านไปยังการเรียกกลับเมื่อถูกเรียกคุณสามารถทำได้ (Firefox และ Chrome ปัจจุบัน IE11 + น่าจะเป็น Edge ไม่ใช่ IE8 หรือ IE9 ไม่รู้เกี่ยวกับ 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);
        */
    });
}

หากคุณใช้ฟังก์ชัน ES2015 + ลูกศรสิ่งนี้จะกระชับมากขึ้น:

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

หรือแม้กระทั่ง

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

ยกเลิกได้ล่าช้าด้วยค่า

หากคุณต้องการยกเลิกการหมดเวลาคุณไม่สามารถคืนสัญญาได้laterเนื่องจากไม่สามารถยกเลิกสัญญาได้

แต่เราสามารถส่งคืนวัตถุได้อย่างง่ายดายด้วยcancelวิธีการและตัวเข้าถึงสำหรับคำสัญญาและปฏิเสธสัญญาเมื่อยกเลิก:

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 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);


คำตอบเดิมจากปี 2014

โดยปกติคุณจะมีคลังคำสัญญา (อันที่คุณเขียนเองหรือหนึ่งในห้องสมุดที่มีอยู่) โดยปกติไลบรารีนั้นจะมีออบเจ็กต์ที่คุณสามารถสร้างและ "แก้ไข" ในภายหลังและอ็อบเจ็กต์นั้นจะมี "สัญญา" ที่คุณจะได้รับ

จากนั้นlaterจะมีลักษณะดังนี้:

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

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

ในความคิดเห็นเกี่ยวกับคำถามฉันถามว่า:

คุณกำลังพยายามสร้างห้องสมุดสัญญาของคุณเองหรือไม่?

และคุณพูด

ฉันไม่ได้ แต่ฉันเดาว่าตอนนี้นั่นคือสิ่งที่ฉันพยายามเข้าใจจริงๆ ว่าห้องสมุดจะทำอย่างไร

เพื่อช่วยในการทำความเข้าใจต่อไปนี้เป็นตัวอย่างพื้นฐานที่ไม่สอดคล้องกับ Promises-A จากระยะไกล: 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>

modernjavascript.blogspot.com/2013/08/…ที่เกี่ยวข้อง :)
Benjamin Gruenbaum

คำตอบของคุณไม่สามารถจัดการกับการยกเลิกหมดเวลา
Alexander Danilov

@AlexanderDanilov: สัญญาไม่สามารถยกเลิกได้ คุณสามารถเขียนฟังก์ชันที่ส่งคืนอ็อบเจ็กต์ด้วยเมธอดการยกเลิกและตัวเข้าถึงสำหรับสัญญาแยกต่างหากจากนั้นปฏิเสธสัญญาหากมีการเรียกวิธีการยกเลิก ...
TJ Crowder

1
@AlexanderDanilov: ฉันไปข้างหน้าและเพิ่มหนึ่ง
TJ Crowder

1
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(cb());
    }, delay);
  });

เราสามารถส่ง 'cb fxn' แบบกำหนดเองได้เช่นนี้👆🏽


0

นี่ไม่ใช่คำตอบสำหรับคำถามเดิม แต่เนื่องจากคำถามเดิมไม่ใช่ปัญหาในโลกแห่งความเป็นจริงจึงไม่ควรเป็นปัญหา ฉันพยายามอธิบายให้เพื่อนฟังว่าสัญญาใน JavaScript คืออะไรและความแตกต่างระหว่างคำสัญญาและการโทรกลับ

รหัสด้านล่างใช้เป็นคำอธิบาย:

//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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.