जावास्क्रिप्ट, Node.js: Array.forEach अतुल्यकालिक है?


378

मेरे पास Array.forEachजावास्क्रिप्ट के मूल कार्यान्वयन के बारे में एक प्रश्न है : क्या यह अतुल्यकालिक व्यवहार करता है? उदाहरण के लिए, यदि मैं फोन करता हूं:

[many many elements].forEach(function () {lots of work to do})

क्या यह गैर-अवरुद्ध होगा?


जवाबों:


392

नहीं, यह अवरुद्ध है। एल्गोरिथ्म के विनिर्देश पर एक नज़र है

हालाँकि MDN पर कार्यान्वयन को समझना आसान है :

if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp */)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in t)
        fun.call(thisp, t[i], i, t);
    }
  };
}

यदि आपको प्रत्येक तत्व के लिए बहुत सारे कोड निष्पादित करने हैं, तो आपको एक अलग दृष्टिकोण का उपयोग करने पर विचार करना चाहिए:

function processArray(items, process) {
    var todo = items.concat();

    setTimeout(function() {
        process(todo.shift());
        if(todo.length > 0) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
}

और फिर इसके साथ कॉल करें:

processArray([many many elements], function () {lots of work to do});

यह तब गैर-अवरुद्ध होगा। इसका उदाहरण उच्च प्रदर्शन जावास्क्रिप्ट से लिया गया है ।

एक अन्य विकल्प वेब कर्मचारी हो सकता है ।


37
यदि आप Node.js का उपयोग कर रहे हैं, तो setTimeout के बजाय प्रक्रिया.
nextTick

28
तकनीकी तौर पर, "अवरुद्ध" नहीं है, क्योंकि सीपीयू कभी सोता नहीं है। यह सिंक्रोनस और सीपीयू-बाउंड है, जो नोड एप से घटनाओं के प्रति संवेदनशील होने की उम्मीद करते हुए "ब्लॉकिंग" की तरह महसूस कर सकता है।
डेव डॉपसन

3
async शायद यहाँ एक अधिक उपयुक्त समाधान होगा (वास्तव में बस किसी ने उत्तर के रूप में पोस्ट किया है!)।
जेम्स

6
मैंने इस जवाब पर भरोसा किया, लेकिन यह कुछ मामलों में गलत लगता है। उदाहरण के लिए बयानों पर ब्लॉक नहींforEach करता है और आपको लूप का उपयोग करना चाहिए : stackoverflow.com/questions/37962880/…awaitfor
रिचर्ड

3
@ रीचर्ड: बिल्कुल। आप केवल awaitअंदर के asyncकार्यों का उपयोग कर सकते हैं । लेकिन forEachपता नहीं है कि async फ़ंक्शन क्या हैं। ध्यान रखें कि async फ़ंक्शन केवल वादे वापस करने वाले फ़ंक्शंस हैं। क्या आप forEachकॉलबैक से लौटे एक वादे को संभालने की उम्मीद करेंगे ? forEachकॉलबैक से वापसी मूल्य को पूरी तरह से अनदेखा करता है। यह केवल एक async कॉलबैक को संभालने में सक्षम होगा यदि यह स्वयं async था।
फेलिक्स क्लिंग

80

यदि आपको एसिंक्रोनस-फ्रेंडली संस्करण की आवश्यकता है Array.forEach, तो वे Node.js 'async' मॉड्यूल में उपलब्ध हैं: http://github.com/caolan/async ... एक बोनस के रूप में यह मॉड्यूल ब्राउज़र में भी काम करता है ।

async.each(openFiles, saveFile, function(err){
    // if any of the saves produced an error, err would equal that error
});

2
यदि आपको यह सुनिश्चित करने की आवश्यकता है कि एक समय में (केवल संग्रह के क्रम में) async opeartion चलाया जाता है , तो आपको eachSeriesइसके बजाय उपयोग करना होगा ।
मैटपॉप

@ जॉनकेनडी मैंने आपको पहले देखा है!
Xsmael

16

नोड में वास्तव में भारी गणना करने के लिए एक सामान्य पैटर्न है जो आपके लिए लागू हो सकता है ...

नोड सिंगल-थ्रेडेड है (एक जानबूझकर डिजाइन विकल्प के रूप में, देखें कि Node.js क्या है? ); इसका मतलब है कि यह केवल एक कोर का उपयोग कर सकता है। आधुनिक बक्सों में 8, 16 या अधिक कोर हैं, इसलिए यह मशीन के 90%% को निष्क्रिय कर सकता है। एक REST सेवा के लिए सामान्य पैटर्न प्रति नोड एक नोड प्रक्रिया को फायर करना है, और इनको http://nginx.org/ जैसे स्थानीय लोड बैलेंसर के पीछे रखना है ।

एक बच्चे के लिए धन्यवाद - आप जो करने की कोशिश कर रहे हैं, उसके लिए एक और सामान्य पैटर्न है, भारी उठाने के लिए एक बच्चे की प्रक्रिया को बंद करना। उल्टा यह है कि बच्चे की प्रक्रिया पृष्ठभूमि में भारी गणना कर सकती है, जबकि आपकी मूल प्रक्रिया अन्य घटनाओं के लिए उत्तरदायी है। पकड़ यह है कि आप इस बच्चे की प्रक्रिया के साथ स्मृति को साझा नहीं कर सकते हैं / नहीं करना चाहिए (गर्भनिरोधक और कुछ देशी कोड के बिना नहीं); आपको संदेश पास करना होगा। यह खूबसूरती से काम करेगा यदि आपके इनपुट और आउटपुट डेटा का आकार उस संगणना की तुलना में छोटा है जिसे निष्पादित किया जाना चाहिए। तुम भी एक बच्चे नोड .js प्रक्रिया को आग लगा सकते हैं और उसी कोड का उपयोग कर सकते हैं जो आप पहले उपयोग कर रहे थे।

उदाहरण के लिए:

var child_process = आवश्यकता ('child_process');
समारोह run_in_child (सरणी, cb) {
    var प्रक्रिया = child_process.exec ('नोड libfn.js', फंक्शन (इरेट, स्टडआउट, स्टैडर) {
        var आउटपुट = JSON.parse (stdout);
        सीबी (इरेट, आउटपुट);
    });
    process.stdin.write (JSON.stringify (सरणी), 'utf8');
    process.stdin.end ();
}

11
बस स्पष्ट होने के लिए ... नोड एकल पिरोया नहीं है, लेकिन आपके जावास्क्रिप्ट का निष्पादन है। आईओ और क्या अलग धागे पर नहीं चलता है।
ब्रैड

3
@ ब्रैड - हो सकता है। यह कार्यान्वयन पर निर्भर है। उपयुक्त कर्नेल समर्थन के साथ, नोड और कर्नेल के बीच का इंटरफ़ेस ईवेंट-आधारित kqueue (mac), epoll (linux), IO पूर्ण पोर्ट (विंडोज़) हो सकता है। गिरावट के रूप में, थ्रेड्स का एक पूल भी काम करता है। आपका मूल बिंदु हालांकि सही है। निम्न-स्तरीय नोड कार्यान्वयन में कई थ्रेड हो सकते हैं। लेकिन वे सीधे जेएस उपयोगकर्ता के लिए उन्हें उजागर नहीं करेंगे क्योंकि यह संपूर्ण भाषा मॉडल को तोड़ देगा।
डेव डॉपसन

4
सही है, मैं सिर्फ स्पष्ट कर रहा हूं क्योंकि अवधारणा ने बहुतों को भ्रमित किया है।
ब्रैड

6

Array.forEachअभिकलन सामान की प्रतीक्षा नहीं करने के लिए है, और एक ईवेंट लूप में अतुल्यकालिक बनाने के लिए कुछ भी प्राप्त नहीं किया जा सकता है (यदि आपके पास मल्टी-कोर कम्प्यूटेशन की आवश्यकता है, तो वेबवर्क मल्टीप्रोसेसिंग जोड़ते हैं)। यदि आप कई कार्यों को समाप्त करने के लिए इंतजार करना चाहते हैं, तो एक काउंटर का उपयोग करें, जिसे आप एक अर्ध-श्रेणी में लपेट सकते हैं।


5

2018-10-11 को संपादित करें: ऐसा लगता है कि एक अच्छा मौका है जो नीचे वर्णित मानक के माध्यम से नहीं जा सकता है, एक विकल्प के रूप में पाइपलाइनिंग पर विचार करें (बिल्कुल वैसा ही व्यवहार नहीं करता है लेकिन एक समान जागीर में तरीकों को लागू किया जा सकता है)।

यह ठीक यही है कि मैं es7 के बारे में उत्साहित हूं, भविष्य में आप नीचे दिए गए कोड की तरह कुछ कर पाएंगे (कुछ चश्मा पूरा नहीं है इसलिए सावधानी के साथ उपयोग करें, मैं इसे अद्यतित रखने की कोशिश करूंगा)। लेकिन मूल रूप से नए :: bind ऑपरेटर का उपयोग करते हुए, आप किसी ऑब्जेक्ट पर एक विधि चलाने में सक्षम होंगे जैसे कि ऑब्जेक्ट के प्रोटोटाइप में विधि होती है। उदाहरण [वस्तु] :: [विधि] जहां आम तौर पर आप [वस्तु] कहते हैं। [ObjectsMethod]

आज (24-जुलाई -16) ऐसा करने के लिए ध्यान दें और यह सभी ब्राउज़रों में काम करता है, आपको निम्न कार्यक्षमता के लिए अपने कोड को ट्रांसपाइल करना होगा: आयात / निर्यात , एरो फ़ंक्शंस , वादे , Async / Await और सबसे महत्वपूर्ण रूप से फ़ंक्शन बाइंड । नीचे दिए गए कोड को केवल फ़ंक्शन बाइंड का उपयोग करने के लिए संशोधित किया जा सकता है यदि नेस्सेरी, यह सब कार्यक्षमता बबेल का उपयोग करके आज बड़े करीने से उपलब्ध है ।

YourCode.js (जहां ' बहुत सारे काम करने के लिए ' बस एक वादा वापस करना होगा, इसे हल करना जब अतुल्यकालिक काम पूरा हो जाएगा।)

import { asyncForEach } from './ArrayExtensions.js';

await [many many elements]::asyncForEach(() => lots of work to do);

ArrayExtensions.js

export function asyncForEach(callback)
{
    return Promise.resolve(this).then(async (ar) =>
    {
        for(let i=0;i<ar.length;i++)
        {
            await callback.call(ar, ar[i], i, ar);
        }
    });
};

export function asyncMap(callback)
{
    return Promise.resolve(this).then(async (ar) =>
    {
        const out = [];
        for(let i=0;i<ar.length;i++)
        {
            out[i] = await callback.call(ar, ar[i], i, ar);
        }
        return out;
    });
};

1

यह थर्ड पार्टी लिबास की आवश्यकता के बिना उपयोग करने के लिए एक छोटा एसिंक्रोनस फ़ंक्शन है

Array.prototype.each = function (iterator, callback) {
    var iterate = function () {
            pointer++;
            if (pointer >= this.length) {
                callback();
                return;
            }
            iterator.call(iterator, this[pointer], iterate, pointer);
    }.bind(this),
        pointer = -1;
    iterate(this);
};

यह अतुल्यकालिक कैसे है? AFAIK #call तुरंत अमल करेगा?
जाइल्स विलियम्स

1
बेशक तुरंत, लेकिन आपके पास सभी पुनरावृत्तियों को पूरा करने के लिए कॉलबैक फ़ंक्शन है। यहाँ "पुनरावृति" तर्क कॉल-बैक के साथ एक नोड-शैली async फ़ंक्शन है। यह async.each विधि के समान है
Rax Wunter

3
मैं नहीं देखता कि यह कैसे async है। कॉल या आवेदन तुल्यकालिक हैं। कॉलबैक होने से यह
असिंचित

जावास्क्रिप्ट में जब लोग async कहते हैं, तो उनका मतलब है कि कोड निष्पादन मुख्य घटना लूप (उर्फ को ब्लॉक नहीं करता है, यह कोड के एक लाइन पर अटक गई असफलता नहीं करता है)। सिर्फ कॉलबैक डालने से कोड असिंक्स नहीं बनता है, इसे किसी प्रकार के इवेंट लूप को रिलीज़ करने के लिए उपयोग करना पड़ता है जैसे कि सेटटाइमआउट या सेटइंटरवल। चूंकि आप उन लोगों के लिए इंतजार कर रहे समय को पूरा करने के बाद, अन्य कोड बिना किसी रुकावट के चला सकते हैं।
वासिलेविच

0

प्रत्येक लूप के लिए आसान अतुल्यकालिक के लिए npm पर एक पैकेज है

var forEachAsync = require('futures').forEachAsync;

// waits for one request to finish before beginning the next 
forEachAsync(['dogs', 'cats', 'octocats'], function (next, element, index, array) {
  getPics(element, next);
  // then after all of the elements have been handled 
  // the final callback fires to let you know it's all done 
  }).then(function () {
    console.log('All requests have finished');
});

इसके अलावा एक और बदलाव forAllAsync


0

उदाहरण के लिए इस तरह के समाधान को भी कोड करना संभव है:

 var loop = function(i, data, callback) {
    if (i < data.length) {
        //TODO("SELECT * FROM stackoverflowUsers;", function(res) {
            //data[i].meta = res;
            console.log(i, data[i].title);
            return loop(i+1, data, errors, callback);
        //});
    } else {
       return callback(data);
    }
};

loop(0, [{"title": "hello"}, {"title": "world"}], function(data) {
    console.log("DONE\n"+data);
});

दूसरी ओर, यह "के लिए" की तुलना में बहुत धीमा है।

अन्यथा, उत्कृष्ट Async लाइब्रेरी यह कर सकती है: https://caolan.github.io/async/docs.html#each


0

यहाँ एक छोटा सा उदाहरण है जिसे आप इसका परीक्षण करने के लिए चला सकते हैं:

[1,2,3,4,5,6,7,8,9].forEach(function(n){
    var sum = 0;
    console.log('Start for:' + n);
    for (var i = 0; i < ( 10 - n) * 100000000; i++)
        sum++;

    console.log('Ended for:' + n, sum);
});

यह कुछ इस तरह का उत्पादन करेगा (यदि इसमें बहुत कम / बहुत समय लगता है, पुनरावृत्तियों की संख्या में वृद्धि / कमी करें):

(index):48 Start for:1
(index):52 Ended for:1 900000000
(index):48 Start for:2
(index):52 Ended for:2 800000000
(index):48 Start for:3
(index):52 Ended for:3 700000000
(index):48 Start for:4
(index):52 Ended for:4 600000000
(index):48 Start for:5
(index):52 Ended for:5 500000000
(index):48 Start for:6
(index):52 Ended for:6 400000000
(index):48 Start for:7
(index):52 Ended for:7 300000000
(index):48 Start for:8
(index):52 Ended for:8 200000000
(index):48 Start for:9
(index):52 Ended for:9 100000000
(index):45 [Violation] 'load' handler took 7285ms

यह तब भी होगा जब आप async.foreach या किसी अन्य समानांतर विधि को लिखेंगे। क्योंकि लूप के लिए एक IO प्रक्रिया नहीं है क्योंकि Nodejs हमेशा इसे सिंक्रोनाइज़ करेंगे।
सुधांशु गौर

-2

Bluebird लाइब्रेरी के Promise.each का उपयोग करें ।

Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise

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

यदि सभी पुनरावृत्तियां सफलतापूर्वक हल हो जाती हैं, तो Promise.each मूल सरणी को निराधार रूप से हल करता है । हालाँकि, यदि कोई पुनरावृति अस्वीकार या त्रुटियां करता है, Promise.each तुरंत निष्पादन को समाप्त कर देता है और आगे पुनरावृत्तियों को संसाधित नहीं करता है। मूल सरणी के बजाय इस मामले में त्रुटि या अस्वीकृत मान लौटाया जाता है।

इस विधि का उपयोग साइड इफेक्ट के लिए किया जाता है।

var fileNames = ["1.txt", "2.txt", "3.txt"];

Promise.each(fileNames, function(fileName) {
    return fs.readFileAsync(fileName).then(function(val){
        // do stuff with 'val' here.  
    });
}).then(function() {
console.log("done");
});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.