वादा - क्या यह एक वादा रद्द करने के लिए मजबूर करना संभव है


95

मैं अपने सभी नेटवर्क डेटा पुनर्प्राप्ति का प्रबंधन करने के लिए ES6 वादों का उपयोग करता हूं और कुछ परिस्थितियां हैं जहां मुझे उन्हें रद्द करने के लिए मजबूर करने की आवश्यकता है।

मूल रूप से परिदृश्य ऐसा है कि मेरे पास यूआई पर एक टाइप-फॉरवर्ड खोज है, जहां बैकएंड को अनुरोधित किया गया है, आंशिक इनपुट के आधार पर खोज को पूरा करना है। हालांकि इस नेटवर्क अनुरोध (# 1) में थोड़ा समय लग सकता है, उपयोगकर्ता टाइप करना जारी रखता है जो अंततः किसी अन्य बैकएंड कॉल (# 2) को चालू करता है

यहाँ # 2 स्वाभाविक रूप से # 1 से अधिक पूर्वता लेता है, इसलिए मैं # 1 रैपिंग अनुरोध को रद्द करना चाहूंगा। मेरे पास पहले से ही डेटा लेयर में सभी वादों का एक कैश है, इसलिए मैं सैद्धांतिक रूप से इसे पुनः प्राप्त कर सकता हूं क्योंकि मैं # 2 के लिए एक प्रॉमिस सबमिट करने का प्रयास कर रहा हूं।

लेकिन कैश से पुनर्प्राप्त करने के बाद मैं # 1 वादा कैसे रद्द करूं?

क्या कोई दृष्टिकोण सुझा सकता है?


2
क्या यह एक विकल्प है कि डिबोस फ़ंक्शन के कुछ बराबर का उपयोग करने के लिए अक्सर ट्रिगर न करें और रूकावट वाले अनुरोध बनें? कहो 300 एमएस देरी की चाल होगा। - उदाहरण के लिए Lodash कार्यान्वयन में से एक है lodash.com/docs#debounce
Shershen

यह तब है जब बेकन और आरएक्स जैसी चीजें उपयोगी होती हैं।
एलक्लेनर्स

@shershen हाँ - हमारे पास यह है, लेकिन यह यूआई मुद्दे के बारे में इतना नहीं है.. सर्वर क्वेरी में थोड़ा समय लग सकता है, इसलिए मैं वादों को रद्द करने में सक्षम होना चाहता हूं ...
मूनवॉकर


Rxjs से वेधशाला का प्रयास करें
FieryCod

जवाबों:


173

नहीं, हम अभी तक ऐसा नहीं कर सकते।

ES6 वादों रद्द समर्थन नहीं करते अभी तक । यह अपने रास्ते पर है, और इसका डिज़ाइन कुछ ऐसा है जिस पर बहुत से लोगों ने वास्तव में कड़ी मेहनत की है। ध्वनि निरस्तीकरण शब्दार्थ सही पाने के लिए कठिन हैं और यह कार्य प्रगति पर है। जीएच पर "भ्रूण" रेपो, एस्किस्कस और कई अन्य रिपोज पर दिलचस्प बहसें हैं, लेकिन अगर मैं आप होता तो मैं धैर्य रखता।

लेकिन, लेकिन, लेकिन .. रद्द करना वास्तव में महत्वपूर्ण है!

यह है, मामले की वास्तविकता रद्द करना क्लाइंट-साइड प्रोग्रामिंग में वास्तव में एक महत्वपूर्ण परिदृश्य है। आपके द्वारा वेब अनुरोधों को निरस्त करने जैसे मामले महत्वपूर्ण हैं और वे हर जगह हैं।

तो ... भाषा ने मुझे डरा दिया!

हाँ, इस बारे में खेद है। प्रॉमिस को आगे की चीजें निर्दिष्ट करने से पहले पहले प्राप्त करना था - इसलिए वे डोम के माध्यम से कल्पना करने के लिए, जैसे कुछ उपयोगी सामान के बिना गए .finallyऔर .cancel- यह अपने रास्ते पर है। रद्द करना है नहीं एक बाद का विचार यह सिर्फ एक समय बाधा और एपीआई डिजाइन करने के लिए एक और अधिक पुनरावृत्ति तरीका है।

तो मै क्या कर सकता हूँ?

आपके पास कई विकल्प हैं:

  • ब्लूबर्ड जैसी थर्ड पार्टी लाइब्रेरी का उपयोग करें जो कल्पना से बहुत तेज गति से आगे बढ़ सकती है और इस तरह से रद्द करने के साथ-साथ अन्य अच्छाइयों का एक गुच्छा भी है - यही व्हाट्सएप जैसी बड़ी कंपनियां करती हैं।
  • एक रद्दकरण टोकन पास करें ।

थर्ड पार्टी लाइब्रेरी का उपयोग करना बहुत स्पष्ट है। एक टोकन के लिए, आप अपने तरीके से एक फंक्शन ले सकते हैं और फिर उसे कॉल कर सकते हैं, जैसे:

function getWithCancel(url, token) { // the token is for cancellation
   var xhr = new XMLHttpRequest;
   xhr.open("GET", url);
   return new Promise(function(resolve, reject) {
      xhr.onload = function() { resolve(xhr.responseText); });
      token.cancel = function() {  // SPECIFY CANCELLATION
          xhr.abort(); // abort request
          reject(new Error("Cancelled")); // reject the promise
      };
      xhr.onerror = reject;
   });
};

जो आपको करने देगा:

var token = {};
var promise = getWithCancel("/someUrl", token);

// later we want to abort the promise:
token.cancel();

आपका वास्तविक उपयोग मामला - last

यह टोकन दृष्टिकोण के साथ बहुत कठिन नहीं है:

function last(fn) {
    var lastToken = { cancel: function(){} }; // start with no op
    return function() {
        lastToken.cancel();
        var args = Array.prototype.slice.call(arguments);
        args.push(lastToken);
        return fn.apply(this, args);
    };
}

जो आपको करने देगा:

var synced = last(getWithCancel);
synced("/url1?q=a"); // this will get canceled 
synced("/url1?q=ab"); // this will get canceled too
synced("/url1?q=abc");  // this will get canceled too
synced("/url1?q=abcd").then(function() {
    // only this will run
});

और नहीं, बेकन और आरएक्स जैसे पुस्तकालय यहां चमकते नहीं हैं क्योंकि वे अवलोकन योग्य पुस्तकालय हैं, उनके पास केवल वही लाभ है जो उपयोगकर्ता के स्तर का वादा करता है पुस्तकालयों की कल्पना नहीं है। मुझे लगता है कि जब हम पर्यवेक्षक मूल निवासी होंगे, तो हम ES2016 में इंतजार करेंगे और देखेंगे। हालांकि वे टाइपिंगहेड के लिए निफ्टी हैं


28
बेंजामिन, वास्तव में आपके उत्तर को पढ़ने में बहुत मजा आया। बहुत अच्छी तरह से सोचा, संरचित, स्पष्ट और अच्छे व्यावहारिक उदाहरण और विकल्प के साथ। वास्तव में सहायक है। धन्यवाद।
मूनवॉकर

चरण 1 प्रस्ताव के रूप में @FranciscoPresencia रद्दकरण टोकन रास्ते में हैं।
बेंजामिन ग्रुएनबाम

हम इस टोकन आधारित रद्दकरण पर कहां तक ​​पढ़ सकते हैं? प्रस्ताव कहां है?
नुकसान

@ धर्म का प्रस्ताव स्टेज 1 पर मर चुका है
बेंजामिन ग्रुएनबाम

1
मुझे रॉन के काम से प्यार है, लेकिन मुझे लगता है कि हमें पुस्तकालयों के लिए सिफारिशें करने से पहले थोड़ा इंतजार करना चाहिए जो लोग अभी तक उपयोग नहीं कर रहे हैं:] लिंक के लिए धन्यवाद हालांकि मैं इसकी जांच करूंगा!
बेंजामिन ग्रुएनबाम

24

रद्द करने योग्य वादों के मानक प्रस्ताव विफल हो गए हैं।

एक वादा यह पूरा करने के लिए async कार्रवाई के लिए एक नियंत्रण सतह नहीं है; उपभोक्ता के साथ मालिक को भ्रमित करता है इसके बजाय, एसिंक्रोनस फ़ंक्शंस बनाएं जो कुछ पारित-किए गए टोकन के माध्यम से रद्द किए जा सकते हैं।

एक और वादा एक ठीक टोकन बनाता है, जिसे लागू करना आसान है Promise.race:

उदाहरण:Promise.race पिछली श्रृंखला के प्रभाव को रद्द करने के लिए उपयोग करें :

let cancel = () => {};

input.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancel();
  let p = new Promise(resolve => cancel = resolve);
  Promise.race([p, getSearchResults(term)]).then(results => {
    if (results) {
      console.log(`results for "${term}"`,results);
    }
  });
}

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
  });
}
Search: <input id="input">

यहां हम undefinedपरिणाम खोजकर और इसके लिए परीक्षण करके पिछली खोजों को "रद्द" कर रहे हैं , लेकिन हम "CancelledError"इसके बजाय आसानी से अस्वीकार करने की कल्पना कर सकते हैं ।

बेशक यह वास्तव में नेटवर्क खोज को रद्द नहीं करता है, लेकिन इसकी एक सीमा है fetch। यदि fetchवाद के रूप में रद्द वादे को लेना था, तो यह नेटवर्क गतिविधि को रद्द कर सकता है।

मैंने इस "एस्काउट वादा पैटर्न" को प्रस्तावित किया है , सटीक चर्चा पर, fetchयह सुझाव देने के लिए कि ऐसा करते हैं।


@jib मेरे संशोधन को क्यों अस्वीकार करता है? मैं इसे स्पष्ट करता हूं।
एलेनिल्ली

8

मैंने मोज़िला जेएस संदर्भ की जाँच की है और यह पाया है:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race

चलो पता करते हैं:

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

हमारे पास यहाँ p1 है, और P2 Promise.race(...)को तर्कों के रूप में रखा गया है, यह वास्तव में नए संकल्प का वादा कर रहा है, जो आपको आवश्यक है।


नीस - यह शायद वही है जो मुझे चाहिए। मुझे इसे एक मौका और देना होगा।
मूनवॉकर

अगर आपको इसमें कोई समस्या है तो आप यहां कोड पेस्ट कर सकते हैं इसलिए मैं आपकी सहायता कर सकता हूं :)
निकोला-मिल्झकोविक

6
इसे आजमाया। काफी नहीं है। यह सबसे तेज़ वादा का समाधान करता है ... मुझे हमेशा नवीनतम प्रस्तुत हल करने की आवश्यकता है अर्थात बिना किसी पुराने वादे को रद्द करें ..
मूनवॉककर

1
इस तरह से अन्य सभी वादे अब संभाले नहीं जाते, आप वास्तव में एक वादा रद्द नहीं कर सकते।
निकोला-मिल्झकोविक

मैंने इसकी कोशिश की, दूसरा वादा (इस पूर्व में एक) इस प्रक्रिया से बाहर निकलने की अनुमति नहीं देता :(
मोर्टेज़ा अलताई

3

Node.js और इलेक्ट्रॉन के लिए, मैं अत्यधिक जावास्क्रिप्ट (Prex) के लिए प्रॉमिस एक्सटेंशन्स का उपयोग करने की सलाह दूंगा । इसके लेखक रॉन बक्टन प्रमुख टाइपस्क्रिप्ट इंजीनियरों में से एक हैं और वर्तमान TC39 के ECMAScript रद्द करने के प्रस्ताव के पीछे भी आदमी है । पुस्तकालय अच्छी तरह से प्रलेखित है और संभावना है कि कुछ प्रॉक्स मानक के अनुरूप होंगे।

एक व्यक्तिगत नोट पर और C # बैकग्राउंड से आने पर, मुझे यह तथ्य बहुत पसंद है कि Prex को Managed Threads फ्रेमवर्क में मौजूदा रद्दीकरण पर आधारित किया गया है, अर्थात CancellationTokenSource/ CancellationToken.NET APIs के साथ लिए गए दृष्टिकोण पर आधारित है । मेरे अनुभव में, वे प्रबंधित ऐप्स में मजबूत रद्दीकरण तर्क को लागू करने के लिए बहुत आसान हैं।

मैंने यह भी सत्यापित किया कि ब्राउजर का उपयोग करके Prex को बंडल करके एक ब्राउज़र के भीतर काम किया जाए

यहाँ रद्द करने के साथ एक देरी का एक उदाहरण है ( सार और RunKit , का उपयोग कर Prex इसके लिए CancellationTokenऔर Deferred):

// by @noseratio
// https://gist.github.com/noseratio/141a2df292b108ec4c147db4530379d2
// https://runkit.com/noseratio/cancellablepromise

const prex = require('prex');

/**
 * A cancellable promise.
 * @extends Promise
 */
class CancellablePromise extends Promise {
  static get [Symbol.species]() { 
    // tinyurl.com/promise-constructor
    return Promise; 
  }

  constructor(executor, token) {
    const withCancellation = async () => {
      // create a new linked token source 
      const linkedSource = new prex.CancellationTokenSource(token? [token]: []);
      try {
        const linkedToken = linkedSource.token;
        const deferred = new prex.Deferred();
  
        linkedToken.register(() => deferred.reject(new prex.CancelError()));
  
        executor({ 
          resolve: value => deferred.resolve(value),
          reject: error => deferred.reject(error),
          token: linkedToken
        });

        await deferred.promise;
      } 
      finally {
        // this will also free all linkedToken registrations,
        // so the executor doesn't have to worry about it
        linkedSource.close();
      }
    };

    super((resolve, reject) => withCancellation().then(resolve, reject));
  }
}

/**
 * A cancellable delay.
 * @extends Promise
 */
class Delay extends CancellablePromise {
  static get [Symbol.species]() { return Promise; }

  constructor(delayMs, token) {
    super(r => {
      const id = setTimeout(r.resolve, delayMs);
      r.token.register(() => clearTimeout(id));
    }, token);
  }
}

// main
async function main() {
  const tokenSource = new prex.CancellationTokenSource();
  const token = tokenSource.token;
  setTimeout(() => tokenSource.cancel(), 2000); // cancel after 2000ms

  let delay = 1000;
  console.log(`delaying by ${delay}ms`); 
  await new Delay(delay, token);
  console.log("successfully delayed."); // we should reach here

  delay = 2000;
  console.log(`delaying by ${delay}ms`); 
  await new Delay(delay, token);
  console.log("successfully delayed."); // we should not reach here
}

main().catch(error => console.error(`Error caught, ${error}`));

ध्यान दें कि रद्दीकरण एक दौड़ है। यानी, एक वादा सफलतापूर्वक हल किया जा सकता है, लेकिन जब तक आप इसे ( awaitया then) के साथ देखते हैं , तब तक रद्दीकरण को ट्रिगर किया जा सकता है। यह आपके ऊपर है कि आप इस दौड़ को कैसे संभालते हैं, लेकिन यह token.throwIfCancellationRequested()अतिरिक्त समय पर कॉल करने के लिए दर्द नहीं करता है , जैसे मैं ऊपर करता हूं।


1

मुझे हाल ही में इसी तरह की समस्या का सामना करना पड़ा।

मेरे पास एक वादा आधारित ग्राहक था (एक नेटवर्क नहीं) और मैं हमेशा यूआई को सुचारू रखने के लिए उपयोगकर्ता को नवीनतम अनुरोधित डेटा देना चाहता था।

रद्द करने के विचार से जूझने के बाद, Promise.race(...)और Promise.all(..)मुझे बस अपना आखिरी अनुरोध आईडी याद करना शुरू हो गया और जब वादा पूरा हुआ तो मैं केवल अपने डेटा का प्रतिपादन कर रहा था जब उसने अंतिम अनुरोध की आईडी से मिलान किया।

आशा है कि यह किसी की मदद करता है।


Slomski सवाल यह नहीं है कि UI पर क्या दिखाना है। इसके वादे को रद्द करने के बारे में
CyberAbhay


0

आप वादा खत्म करने से पहले अस्वीकार कर सकते हैं:

// Our function to cancel promises receives a promise and return the same one and a cancel function
const cancellablePromise = (promiseToCancel) => {
  let cancel
  const promise = new Promise((resolve, reject) => {
    cancel = reject
    promiseToCancel
      .then(resolve)
      .catch(reject)
  })
  return {promise, cancel}
}

// A simple promise to exeute a function with a delay
const waitAndExecute = (time, functionToExecute) => new Promise((resolve, reject) => {
  timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    resolve(functionToExecute())
  }, timeInMs)
})

// The promise that we will cancel
const fetchURL = () => fetch('https://pokeapi.co/api/v2/pokemon/ditto/')

// Create a function that resolve in 1 seconds. (We will cancel it in 0.5 secs)
const {promise, cancel} = cancellablePromise(waitAndExecute(1, fetchURL))

promise
  .then((res) => {
    console.log('then', res) // This will executed in 1 second
  })
  .catch(() => {
    console.log('catch') // We will force the promise reject in 0.5 seconds
  })

waitAndExecute(0.5, cancel) // Cancel previous promise in 0.5 seconds, so it will be rejected before finishing. Commenting this line will make the promise resolve

दुर्भाग्य से भ्रूण कॉल पहले ही हो चुकी है, इसलिए आप नेटवर्क टैब में कॉल को हल करते देखेंगे। आपका कोड इसे अनदेखा करेगा।


0

बाहरी पैकेज द्वारा प्रदान किए गए प्रॉमिस उपवर्ग का उपयोग करते हुए, यह निम्नानुसार किया जा सकता है: लाइव डेमो

import CPromise from "c-promise2";

function fetchWithTimeout(url, {timeout, ...fetchOptions}= {}) {
    return new CPromise((resolve, reject, {signal}) => {
        fetch(url, {...fetchOptions, signal}).then(resolve, reject)
    }, timeout)
}

const chain= fetchWithTimeout('http://localhost/')
    .then(response => response.json())
    .then(console.log, console.warn);

//chain.cancel(); call this to abort the promise and releated request

-1

चूँकि @jib ने मेरे संशोधन को अस्वीकार कर दिया है, इसलिए मैं यहाँ अपना उत्तर देता हूँ। यह कुछ टिप्पणियों के साथ और अधिक समझने योग्य चर नामों का उपयोग करते हुए @ jib'swser का सिर्फ मॉडिफाई है ।

नीचे मैं सिर्फ दो अलग-अलग तरीकों के उदाहरण दिखाता हूं: एक है संकल्प () दूसरा अस्वीकार है ()

let cancelCallback = () => {};

input.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancelCallback(); //cancel previous promise by calling cancelCallback()

  let setCancelCallbackPromise = () => {
    return new Promise((resolve, reject) => {
      // set cancelCallback when running this promise
      cancelCallback = () => {
        // pass cancel messages by resolve()
        return resolve('Canceled');
      };
    })
  }

  Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
    // check if the calling of resolve() is from cancelCallback() or getSearchResults()
    if (results == 'Canceled') {
      console.log("error(by resolve): ", results);
    } else {
      console.log(`results for "${term}"`, results);
    }
  });
}


input2.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancelCallback(); //cancel previous promise by calling cancelCallback()

  let setCancelCallbackPromise = () => {
    return new Promise((resolve, reject) => {
      // set cancelCallback when running this promise
      cancelCallback = () => {
        // pass cancel messages by reject()
        return reject('Canceled');
      };
    })
  }

  Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
    // check if the calling of resolve() is from cancelCallback() or getSearchResults()
    if (results !== 'Canceled') {
      console.log(`results for "${term}"`, results);
    }
  }).catch(error => {
    console.log("error(by reject): ", error);
  })
}

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
  });
}
Search(use resolve): <input id="input">
<br> Search2(use reject and catch error): <input id="input2">

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.