फ़ंक्शन को फिर से शुरू करने से पहले एक जावास्क्रिप्ट प्रोमिस के लिए कैसे प्रतीक्षा करें?


107

मैं कुछ यूनिट परीक्षण कर रहा हूं। परीक्षण की रूपरेखा किसी पृष्ठ को iFrame में लोड करती है और फिर उस पृष्ठ के विरुद्ध अभिकथन करती है। इससे पहले कि प्रत्येक परीक्षा शुरू होता है, मैं बनाने के एक Promiseजो iFrame के सेट onloadकॉल करने के लिए घटना resolve(), iFrame के सेट src, और रिटर्न वादा।

इसलिए, मैं बस कॉल कर सकता हूं loadUrl(url).then(myFunc), और जो कुछ भी myFuncहै उसे निष्पादित करने से पहले पृष्ठ के लोड होने की प्रतीक्षा करेगा ।

मैं अपने परीक्षणों में पूरे स्थान पर इस तरह के पैटर्न का उपयोग करता हूं (न केवल यूआरएल लोड करने के लिए), मुख्य रूप से डोम में होने वाले बदलावों की अनुमति देने के लिए (उदाहरण के लिए एक बटन पर क्लिक करके नकल करें, और छिपाने और दिखाने के लिए divs की प्रतीक्षा करें)।

इस डिजाइन के लिए नकारात्मक यह है कि मैं उनमें कोड की कुछ पंक्तियों के साथ लगातार अनाम फ़ंक्शन लिख रहा हूं। इसके अलावा, जब मेरे पास काम (क्वेट assert.async()) है, तो वादे को पूरा करने से पहले वादों को पूरा करने वाला परीक्षण कार्य।

मैं सोच रहा था कि क्या कोई तरीका है Promiseया प्रतीक्षा (ब्लॉक / नींद) से एक मूल्य प्राप्त करने के लिए जब तक कि यह हल न हो, .NET के समान IAsyncResult.WaitHandle.WaitOne()। मुझे पता है कि जावास्क्रिप्ट एकल-थ्रेडेड है, लेकिन मैं उम्मीद कर रहा हूं कि इसका मतलब यह नहीं है कि एक फ़ंक्शन उपज नहीं कर सकता है।

संक्षेप में, क्या सही क्रम में परिणामों को थूकने के लिए एक तरीका है?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


यदि आप पुन: प्रयोज्य समारोह में एपेंड कॉल डालते हैं, तो आप DRY को आवश्यकतानुसार () कर सकते हैं। आप मल्टी- पर्पस हैंडलर भी बना सकते हैं, इसे फीड करने के लिए .then(fnAppend.bind(myDiv))स्टीयरिंग () कॉल, जैसे , जो बहुत हद तक चींटियों को काट सकता है।
डंडविस

आप किसके साथ परीक्षण कर रहे हैं? यदि यह एक आधुनिक ब्राउज़र है या आप अपने कोड को ट्रांसपाइल करने के लिए BabelJS जैसे टूल का उपयोग कर सकते हैं तो यह निश्चित रूप से संभव है।
बेंजामिन ग्रुएनबाम

जवाबों:


78

मुझे आश्चर्य हो रहा है कि क्या कोई वादा या प्रतीक्षा (ब्लॉक / नींद) से मूल्य प्राप्त करने का कोई तरीका है जब तक कि यह हल नहीं हो जाता है, .NET के IAsyncResult.WaitHandle.WaitOne () के समान है। मुझे पता है कि जावास्क्रिप्ट एकल-थ्रेडेड है, लेकिन मैं उम्मीद कर रहा हूं कि इसका मतलब यह नहीं है कि एक फ़ंक्शन उपज नहीं कर सकता है।

ब्राउज़रों में जावास्क्रिप्ट की वर्तमान पीढ़ी के पास ऐसा नहीं है wait()या sleep()जो अन्य चीजों को चलाने की अनुमति देता है। तो, आप बस वह नहीं कर सकते जो आप पूछ रहे हैं। इसके बजाय, इसके पास async ऑपरेशंस हैं जो उनकी बात करेंगे और तब आपको कॉल करेंगे जब वे काम कर लेंगे (जैसा कि आप वादों के लिए उपयोग कर रहे हैं)।

इसका कारण जावास्क्रिप्ट का एकल पिरोयापन है। यदि एकल धागा कताई है, तो कोई अन्य जावास्क्रिप्ट निष्पादित नहीं कर सकता है जब तक कि कताई धागा नहीं किया जाता है। ईएस 6 परिचय yieldऔर जनरेटर जो कुछ सहकारी तरकीबों की अनुमति देगा, लेकिन हम उन तरीकों का उपयोग करने में सक्षम हैं जो स्थापित ब्राउज़रों की एक विस्तृत संख्या में उपयोग कर सकते हैं (वे कुछ सर्वर साइड विकास में उपयोग किए जा सकते हैं जहां आप जेएस इंजन को नियंत्रित करते हैं। उसका उपयोग हो रहा है)।


वादा-आधारित कोड का सावधानीपूर्वक प्रबंधन कई async संचालन के लिए निष्पादन के आदेश को नियंत्रित कर सकता है।

मुझे यकीन नहीं है कि मैं ठीक से समझता हूं कि आप अपने कोड में क्या ऑर्डर हासिल करना चाहते हैं, लेकिन आप अपने मौजूदा kickOff()फ़ंक्शन का उपयोग करके ऐसा कुछ कर सकते हैं , और फिर .then()इसे कॉल करने के बाद हैंडलर संलग्न कर सकते हैं:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

यह आउटपुट को गारंटीकृत क्रम में लौटाएगा - इस तरह:

start
middle
end

2018 में अपडेट (इस उत्तर के लिखे जाने के तीन साल बाद):

आप या तो अपने कोड transpile या एक वातावरण में अपने कोड चलने वाले का समर्थन करता है ES7 जैसे का प्रचार करता है asyncऔर await, अब आप उपयोग कर सकते हैं awaitअपने कोड "दिखाई" बनाने के लिए एक वादा के परिणाम की प्रतीक्षा करने के लिए। यह अभी भी वादों के साथ विकसित हो रहा है। यह अभी भी सभी जावास्क्रिप्ट को ब्लॉक नहीं करता है, लेकिन यह आपको फ्रेंडली सिंटैक्स में अनुक्रमिक संचालन लिखने की अनुमति देता है।

चीजों को करने के ईएस 6 तरीके के बजाय:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

तुम यह केर सकते हो:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

3
ठीक है, का उपयोग कर then()मैं क्या कर रहा है। मुझे function() { ... }हर समय लिखना पसंद नहीं है । यह कोड को बंद कर देता है।
dx_over_dt

3
@dfoverdx - जावास्क्रिप्ट में async कोडिंग में हमेशा कॉलबैक शामिल होता है, इसलिए आपको हमेशा एक फ़ंक्शन (अनाम या नाम) को परिभाषित करना होगा। वर्तमान में इसके आसपास कोई रास्ता नहीं है।
jfriend00

2
हालांकि आप इसे और अधिक ट्रिक बनाने के लिए ES6 एरो नोटेशन का उपयोग कर सकते हैं। (foo) => { ... }इसके बजायfunction(foo) { ... }
रॉब एच

1
@RobH कमेंट में जोड़ना अक्सर आप foo => single.expression(here)घुंघराले और गोल कोष्ठक से छुटकारा पाने के लिए भी लिख सकते हैं।
भीगी

3
@dfoverdx - मेरे उत्तर के तीन साल बाद, मैंने इसे ES7 asyncऔर awaitसिंटैक्स के साथ एक विकल्प के रूप में अद्यतन किया जो अब नोड.जेएस और आधुनिक ब्राउज़रों में या ट्रांसपैरर्स के माध्यम से उपलब्ध है।
jfriend00

15

यदि ES2016 का उपयोग कर आप उपयोग कर सकते हैं asyncऔर awaitकुछ ऐसा कर सकते हैं :

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

यदि ES2015 का उपयोग कर आप जेनरेटर का उपयोग कर सकते हैं । अगर आपको सिंटैक्स पसंद नहीं है, तो आप एक asyncउपयोगिता फ़ंक्शन का उपयोग करके इसे अलग कर सकते हैं जैसा कि यहां बताया गया है

यदि ES5 का उपयोग कर रहे हैं, तो आप शायद आपको अधिक नियंत्रण देने के लिए Bluebird जैसी लाइब्रेरी चाहते हैं ।

अंत में, यदि आपका रनटाइम ES2015 का समर्थन करता है, तो पहले से ही निष्पादन आदेश को फेच इंजेक्शन के साथ समानता के साथ संरक्षित किया जा सकता है ।


1
आपका जनरेटर उदाहरण काम नहीं करता है, यह एक धावक की कमी है। कृपया किसी भी अधिक जनरेटर की सिफारिश न करें जब आप ES8 async/ को ट्रांसपाइल कर सकते हैं await
बरगी

3
आपकी प्रतिक्रिया के लिए धन्यवाद। मैंने जेनरेटर उदाहरण को हटा दिया है और संबंधित ओएसएस लाइब्रेरी के साथ एक अधिक व्यापक जनरेटर ट्यूटोरियल से जुड़ा हुआ है।
जोश हबदास

9
यह बस वादा लपेटता है। जब आप इसे चलाते हैं तो async फ़ंक्शन कुछ भी इंतजार नहीं करेगा , यह सिर्फ एक वादा वापस करेगा। async/ के awaitलिए सिर्फ एक और वाक्यविन्यास है Promise.then
रस्टीक्स

आप बिलकुल सही asyncप्रतीक्षा नहीं करेंगे ... जब तक कि यह पैदावार का उपयोग न करे await। ES2016 / ES7 उदाहरण अभी भी SO नहीं चलाया जा सकता है, इसलिए यहां कुछ कोड मैंने तीन साल पहले लिखे थे जो व्यवहार को प्रदर्शित करते थे।
जोश हबदास

7

एक अन्य विकल्प वादा करने के लिए वादों की एक सरणी के लिए प्रतीक्षा करने के लिए Promise.all का उपयोग करना है। नीचे दिए गए कोड से पता चलता है कि सभी वादों को हल करने के लिए कैसे प्रतीक्षा करें और फिर परिणामों के साथ सौदा करने के बाद वे सभी तैयार हैं (जैसा कि प्रश्न का उद्देश्य प्रतीत होता है); हालाँकि, प्रारंभ स्पष्ट रूप से आउटपुट कर सकता है इससे पहले कि मध्य ने समाधान को कॉल करने से पहले उस कोड को जोड़कर हल किया हो।

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


1

आप इसे मैन्युअल रूप से कर सकते हैं। (मुझे पता है, कि महान समाधान नहीं है, लेकिन ..) एक मूल्य नहीं है whileजब तक पाश का उपयोग करेंresult

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

यह प्रतीक्षा करते समय पूरे सीपीयू का उपभोग करेगा।
लुकाज़ गामिंस्की

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