नोड जेएस Promise.all और forEach


120

मेरे पास संरचना की तरह एक सरणी है जो async विधियों को उजागर करती है। Async विधि कॉल सरणी संरचनाएँ जो बदले में अधिक async विधियों को उजागर करती है। मैं इस संरचना से प्राप्त मूल्यों को संग्रहीत करने के लिए एक और JSON ऑब्जेक्ट बना रहा हूं और इसलिए मुझे कॉलबैक में संदर्भों पर नज़र रखने के बारे में सावधान रहने की आवश्यकता है।

मैंने एक ब्रूट फोर्स सॉल्यूशन को कोडित किया है, लेकिन मैं एक अधिक मुहावरेदार या स्वच्छ समाधान सीखना चाहूंगा।

  1. घोंसले के शिकार के स्तर के लिए पैटर्न को दोहराने योग्य होना चाहिए।
  2. मुझे एनक्लोजिंग रूटीन को हल करने के लिए यह निर्धारित करने के लिए वादे या किसी अन्य तकनीक का उपयोग करने की आवश्यकता है।
  3. हर तत्व अनिवार्य रूप से एक async कॉल करना शामिल नहीं करेगा। इसलिए एक नेस्टेड वादे में। मैं बस इंडेक्स के आधार पर अपने JSON सरणी तत्वों को असाइनमेंट नहीं कर सकता। फिर भी, मुझे यह सुनिश्चित करने के लिए वादे की तरह कुछ का उपयोग करने की आवश्यकता है। यह सुनिश्चित करने के लिए कि सभी संपत्ति असाइनमेंट को संलग्न करने की दिनचर्या को हल करने से पहले किया गया है।
  4. मैं ब्लूबर्ड वादे का उपयोग कर रहा हूं लेकिन यह कोई आवश्यकता नहीं है

यहाँ कुछ आंशिक कोड है -

var jsonItems = [];

items.forEach(function(item){

  var jsonItem = {};
  jsonItem.name = item.name;
  item.getThings().then(function(things){
  // or Promise.all(allItemGetThingCalls, function(things){

    things.forEach(function(thing, index){

      jsonItems[index].thingName = thing.name;
      if(thing.type === 'file'){

        thing.getFile().then(function(file){ //or promise.all?

          jsonItems[index].filesize = file.getSize();

यह काम करने वाले स्रोत की कड़ी है जिसे मैं सुधारना चाहता हूं। github.com/pebanfield/change-view-service/blob/master/src/…
user3205931

1
मैं नमूना आप Bluebird उपयोग कर रहे हैं में देखते हैं, Bluebird वास्तव में अपने जीवन में आता है और भी आसान के साथ Promise.map(समवर्ती) और Promise.eachइस मामले में (अनुक्रमिक), भी टिप्पणी Promise.deferहटा दिया गया है - मेरा उत्तर शो में कोड कैसे द्वारा यह से बचने के लिए लौटने का वादा किया। वादे सभी वापसी मूल्यों के बारे में हैं।
बेंजामिन ग्रुएनबाम

जवाबों:


368

यह कुछ सरल नियमों के साथ बहुत सीधा है:

  • जब भी आप एक वादा बनाते हैं then, तो उसे वापस कर दें - कोई भी वादा जिसे आप वापस नहीं करते हैं, बाहर के लिए इंतजार नहीं किया जाएगा।
  • जब भी आप कई वादे करते हैं, .allउन्हें - इस तरह यह सभी वादों का इंतजार करता है और उनमें से कोई भी त्रुटि चुप नहीं रहती है।
  • जब भी आप घोंसला बनाते हैं then, तो आप आम तौर पर बीच में लौट सकतेthen हैं - चेन आमतौर पर अधिकतम 1 स्तर पर होती हैं।
  • जब भी आप IO करते हैं, तो यह एक वादे के साथ होना चाहिए - या तो यह एक वादे में होना चाहिए या फिर इसे पूरा होने का संकेत देने के लिए एक वादे का उपयोग करना चाहिए।

और कुछ सुझाव:

  • मैपिंग के साथ की .mapतुलना में बेहतर हैfor/push - यदि आप किसी फ़ंक्शन के साथ मान मैप कर रहे हैं, तो आप mapएक-एक करके क्रियाओं को लागू करने और परिणामों को एकत्र करने की धारणा को स्पष्ट रूप से व्यक्त कर सकते हैं।
  • यदि यह मुफ़्त है, तो कंज़ेम् शनियल निष्पादन से बेहतर है - समवर्ती चीजों को निष्पादित करना बेहतर है और उनके लिए प्रतीक्षा करना Promise.allएक के बाद एक चीजों को निष्पादित करना है - प्रत्येक अगले से पहले प्रतीक्षा कर रहा है।

ठीक है, तो चलिए शुरू करते हैं:

var items = [1, 2, 3, 4, 5];
var fn = function asyncMultiplyBy2(v){ // sample async action
    return new Promise(resolve => setTimeout(() => resolve(v * 2), 100));
};
// map over forEach since it returns

var actions = items.map(fn); // run the function over all items

// we now have a promises array and we want to wait for it

var results = Promise.all(actions); // pass array of promises

results.then(data => // or just .then(console.log)
    console.log(data) // [2, 4, 6, 8, 10]
);

// we can nest this of course, as I said, `then` chains:

var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then(
    data => Promise.all(data.map(fn))
).then(function(data){
    // the next `then` is executed after the promise has returned from the previous
    // `then` fulfilled, in this case it's an aggregate promise because of 
    // the `.all` 
    return Promise.all(data.map(fn));
}).then(function(data){
    // just for good measure
    return Promise.all(data.map(fn));
});

// now to get the results:

res2.then(function(data){
    console.log(data); // [16, 32, 48, 64, 80]
});

5
आह, आपके नजरिए से कुछ नियम :-)
बरगी जूल

1
@ बर्गी किसी को वास्तव में इन नियमों की एक सूची और वादों पर एक छोटी पृष्ठभूमि बनाना चाहिए। हम इसे शायद bluebirdjs.com पर होस्ट कर सकते हैं।
बेंजामिन ग्रुएनबाम

चूँकि मैं केवल धन्यवाद कहने के लिए नहीं हूँ - यह उदाहरण अच्छा लग रहा है और मुझे मानचित्र सुझाव पसंद है, हालाँकि, वस्तुओं के संग्रह के बारे में क्या करना है जहाँ केवल कुछ के पास async विधियाँ हैं? (मेरी बात 3 से ऊपर) मेरे पास एक विचार था कि मैं प्रत्येक तत्व के लिए पार्सिंग लॉजिक को एक फंक्शन में अमूर्त करूँगा और फिर इसे async कॉल रिस्पांस पर हल करना होगा या जहाँ कोई async कॉल नहीं थी, बस इसे हल करना है। क्या इसका कोई मतलब है?
उपयोगकर्ता 3205931

मुझे नक्शा फ़ंक्शन वापस करने की आवश्यकता है जो कि मैं निर्माण कर रहा हूं, दोनों को वापस करना है और async कॉल का परिणाम मुझे यह सुनिश्चित करने की आवश्यकता है कि या तो यह कैसे करना है - आखिरकार जब मैं एक निर्देशिका चला रहा हूं तो पूरी चीज को पुनरावर्ती होने की आवश्यकता है। संरचना - मैं अभी भी इसे चबा रहा हूँ लेकिन भुगतान किया हुआ काम इस तरह से हो रहा है :(
user3205931

2
@ user3205931 वादे सरल हैं, बजाय आसान है , वह यह है कि वे अन्य सामानों की तरह परिचित नहीं हैं, लेकिन एक बार जब आप उन्हें कर लेते हैं तो वे उपयोग करने के लिए बहुत बेहतर होते हैं। रुको तंग तुम इसे मिल जाएगा :)
बेंजामिन Gruenbaum

42

यहाँ एक सरल उदाहरण है कम का उपयोग कर। यह क्रमिक रूप से चलता है, सम्मिलन क्रम को बनाए रखता है, और ब्लूबर्ड की आवश्यकता नहीं होती है।

/**
 * 
 * @param items An array of items.
 * @param fn A function that accepts an item from the array and returns a promise.
 * @returns {Promise}
 */
function forEachPromise(items, fn) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item);
        });
    }, Promise.resolve());
}

और इसे इस तरह से उपयोग करें:

var items = ['a', 'b', 'c'];

function logItem(item) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            resolve();
        })
    });
}

forEachPromise(items, logItem).then(() => {
    console.log('done');
});

हमने लूप में वैकल्पिक संदर्भ भेजने के लिए इसे उपयोगी पाया है। संदर्भ वैकल्पिक है और सभी पुनरावृत्तियों द्वारा साझा किया गया है।

function forEachPromise(items, fn, context) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item, context);
        });
    }, Promise.resolve());
}

आपका वादा समारोह इस तरह दिखेगा:

function logItem(item, context) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            context.itemCount++;
            resolve();
        })
    });
}

इसके लिए धन्यवाद - आपके समाधान ने मेरे लिए काम किया है जहाँ अन्य (विभिन्न npm लिबास सहित) नहीं है। क्या आपने इसे npm पर प्रकाशित किया है?
सैमएफ

धन्यवाद। फ़ंक्शन मानता है कि सभी वादे हल हो गए हैं। अस्वीकार किए गए वादों को हम कैसे संभालते हैं? इसके अलावा, हम एक मूल्य के साथ सफल वादों को कैसे संभालते हैं?
इलोजी

@oyalhi मैं 'संदर्भ' का उपयोग करने और त्रुटि के लिए मैप किए गए अस्वीकृत इनपुट मापदंडों की एक सरणी जोड़ने का सुझाव दूंगा। यह वास्तव में प्रति उपयोग-मामला है, क्योंकि कुछ सभी शेष वादों को अनदेखा करना चाहेंगे और कुछ नहीं करेंगे। लौटे मूल्य के लिए, आप एक समान दृष्टिकोण का उपयोग भी कर सकते हैं।
स्टीवन स्पंगिन

1

मैं उसी स्थिति से गुज़रा था। मैंने दो Promise.All () का उपयोग करके हल किया।

मुझे लगता है कि वास्तव में अच्छा समाधान था, इसलिए मैंने इसे npm पर प्रकाशित किया: https://www.npmjs.com/package/promise-foreach

मुझे लगता है कि आपका कोड कुछ इस तरह होगा

var promiseForeach = require('promise-foreach')
var jsonItems = [];
promiseForeach.each(jsonItems,
    [function (jsonItems){
        return new Promise(function(resolve, reject){
            if(jsonItems.type === 'file'){
                jsonItems.getFile().then(function(file){ //or promise.all?
                    resolve(file.getSize())
                })
            }
        })
    }],
    function (result, current) {
        return {
            type: current.type,
            size: jsonItems.result[0]
        }
    },
    function (err, newList) {
        if (err) {
            console.error(err)
            return;
        }
        console.log('new jsonItems : ', newList)
    })

0

बस प्रस्तुत समाधान में जोड़ने के लिए, मेरे मामले में मैं उत्पादों की सूची के लिए फायरबेस से कई डेटा प्राप्त करना चाहता था। यहाँ देखें कि मैंने यह कैसे किया:

useEffect(() => {
  const fn = p => firebase.firestore().doc(`products/${p.id}`).get();
  const actions = data.occasion.products.map(fn);
  const results = Promise.all(actions);
  results.then(data => {
    const newProducts = [];
    data.forEach(p => {
      newProducts.push({ id: p.id, ...p.data() });
    });
    setProducts(newProducts);
  });
}, [data]);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.