
जब मैंने नोड.जेएस पर लिखना शुरू किया, तो मुझे दो चीजों से नफरत थी: सभी लोकप्रिय टेंपलेटिंग इंजन और बड़ी संख्या में कॉलबैक। मैंने स्वेच्छा से कॉलबैक का उपयोग किया क्योंकि मैं इवेंट-ओरिएंटेड सर्वर की पूरी शक्ति को समझता था, लेकिन तब से
जनरेटर जावास्क्रिप्ट में दिखाई दिए हैं, और मैं उस दिन का इंतजार कर रहा हूं जब उन्हें लागू किया जाएगा।
और अब यह दिन आ रहा है। आज,
V8 और स्पाइडरमोंकी
में जनरेटर
उपलब्ध हैं , कार्यान्वयन विनिर्देश अपडेट का अनुसरण करता है - यह एक नए युग की सुबह है!
जबकि V8 कमांड लाइन फ्लैग के पीछे जनरेटर जैसे नए हार्मनी फीचर्स को छुपाता है, कुछ समय के लिए ऐसा ही रहेगा। इससे पहले कि वे सभी ब्राउज़रों में उपलब्ध हों, हम आगे बढ़ सकते हैं और जनरेटर के साथ अतुल्यकालिक कोड लिखना सीख सकते हैं। आइए इन तरीकों पर जल्दी प्रयास करें।
आप
नोड 0.11 के
अस्थिर संस्करण को डाउनलोड करके आज उनका उपयोग कर सकते हैं, जो अगला स्थिर संस्करण होगा। नोड शुरू करते समय,
--harmony
या
--harmony-generators
ध्वज को पास करें।
तो आप कॉलबैक नरक से बचाने के लिए जनरेटर का उपयोग कैसे करते हैं? जेनरेटर फ़ंक्शन
yield
का उपयोग करके निष्पादन को रोक सकते हैं, और जब वे फिर से शुरू करते हैं या रोकते हैं, तो परिणाम को अंदर या बाहर पारित कर सकते हैं। इस तरह, हम तब रोक सकते हैं जब कोई फ़ंक्शन किसी अन्य फ़ंक्शन के परिणाम के लिए प्रतीक्षा करता है, बिना कॉलबैक पास किए।
क्या यह मजेदार नहीं है जब मैं हमारी भाषा में भाषा निर्माण की व्याख्या करने की कोशिश करता हूं? कोड में डाइविंग के बारे में कैसे?
जनरेटर मूल बातें
हम अतुल्यकालिक दुनिया में गोता लगाने से पहले एक आदिम जनरेटर को देखते हैं। जनरेटर
function*
साथ घोषित किए जाते हैं:
function* foo(x) { yield x + 1; var y = yield null; return x + y; }
निम्नलिखित एक उदाहरण कॉल है:
var gen = foo(5); gen.next();
अगर मैंने कक्षा में नोट्स लिए, तो मैं लिखूंगा:
- सभी अभिव्यक्तियों में
yield
अनुमति है। - एक जनरेटर कॉल एक नियमित फ़ंक्शन के समान है, लेकिन यह एक जनरेटर ऑब्जेक्ट बनाता है। आपको जनरेटर को फिर से शुरू करने के लिए
next
कॉल करने या send
की आवश्यकता है। जब आप इसे वापस मान भेजना चाहते हैं तो send
उपयोग किया जाता है। gen.next()
gen.send(null)
बराबर है। वहाँ भी gen.throw
जो जनरेटर के अंदर एक अपवाद फेंकता है। - जेनरेटर विधियां शुद्ध मूल मूल्य नहीं लौटाती हैं, लेकिन दो मापदंडों के साथ एक वस्तु:
value
और done
। करने के लिए धन्यवाद done
यह स्पष्ट हो जाता है जब जनरेटर समाप्त हो जाता है, या तो return
या फ़ंक्शन के सरल अंत के साथ, असुविधाजनक StopIteration
अपवाद के बजाय जो पुराने एपीआई में था।
अतुल्यकालिक समाधान # 1: रोकें
उस कॉलबैक नरक के कोड के साथ क्या करना है? ठीक है, अगर हम मनमाने ढंग से फ़ंक्शन रोक सकते हैं। हम अपने अतुल्यकालिक कॉलबैक कोड को चीनी टुकड़ों के साथ सिंक्रोनस-लुकिंग कोड में बदल सकते हैं।
प्रश्न: चीनी क्या है?
सस्पेंड लाइब्रेरी में पहला समाधान प्रस्तावित है। यह बहुत सरल है।
कोड की केवल
16 लाइनें , गंभीरता से।
इस लाइब्रेरी के साथ हमारा कोड कैसा दिखता है:
var suspend = require('suspend'), fs = require('fs'); suspend(function*(resume) { var data = yield fs.readFile(__filename, 'utf8', resume); if(data[0]) { throw data[0]; } console.log(data[1]); })();
suspend
फ़ंक्शन आपके जनरेटर को नियमित फ़ंक्शन में पास करता है जो जनरेटर शुरू करता है। यह जनरेटर के लिए
resume
से
resume
समारोह पारित करता है,
resume
से
resume
समारोह का उपयोग सभी अतुल्यकालिक कॉल के लिए कॉलबैक के रूप में किया जाना चाहिए, यह जनरेटर को त्रुटि और मूल्य ध्वज वाले तर्कों के साथ फिर से शुरू करता है।
resume
और जनरेटर नृत्य दिलचस्प हैं, लेकिन कुछ कमियां हैं। सबसे पहले, दो-तत्व सरणी को वापस प्राप्त करना असुविधाजनक है, यहां तक कि विनाश के साथ भी (
var [err, res] = yield foo(resume)
)। मैं केवल मूल्य वापस करना चाहूंगा, और एक त्रुटि को अपवाद के रूप में फेंक दूंगा, यदि कोई हो। वास्तव में, पुस्तकालय इसका समर्थन करता है, लेकिन एक विकल्प के रूप में, मुझे लगता है कि यह डिफ़ॉल्ट होना चाहिए।
दूसरे, यह हमेशा स्पष्ट रूप से फिर से शुरू करने के लिए असुविधाजनक है, इसके अलावा, यह अनुपयुक्त है जब आप कार्य पूरा होने तक प्रतीक्षा करते हैं। और मुझे अभी भी
callback
जोड़ना है और फ़ंक्शन के अंत में कॉल करना है, जैसा कि आमतौर पर नोड में किया जाता है।
अंत में, आप निष्पादन के अधिक जटिल धागे का उपयोग नहीं कर सकते हैं, उदाहरण के लिए कई समवर्ती कॉल के साथ।
README का दावा है कि अन्य प्रवाह नियंत्रण पुस्तकालय पहले से ही इस समस्या को हल कर रहे हैं, और आपको उनमें से एक के साथ
suspend
उपयोग करना चाहिए, लेकिन मैं प्रवाह नियंत्रण पुस्तकालय देखना चाहूंगा जिसमें जनरेटर समर्थन शामिल है।
लेखक से जुड़ाव : क्रिस्क्वल ने
क्रिएक्सिक्स द्वारा लिखे गए
इस जिस्ट का प्रस्ताव रखा, कॉलबैक-आधारित कोड के लिए एक बेहतर स्टैंड-अलोन जनरेटर हैंडलर है। डिफ़ॉल्ट रूप से त्रुटियों को फेंकना बहुत अच्छा है।
अतुल्यकालिक समाधान # 2: वादा करता है
अतुल्यकालिक निष्पादन प्रवाह को नियंत्रित करने का एक और दिलचस्प तरीका
वादों का उपयोग करना
है । वादा एक वस्तु है जो भविष्य के मूल्य का प्रतिनिधित्व करता है, और आप एक प्रोग्राम द्वारा निष्पादन के कॉलिंग थ्रेड को वादे प्रदान कर सकते हैं जो अतुल्यकालिक व्यवहार का प्रतिनिधित्व करता है।
मैं यहां वादों की व्याख्या नहीं करूंगा, क्योंकि इसमें बहुत अधिक समय लगेगा और इसके अलावा, पहले से ही एक
अच्छी व्याख्या है । हाल ही में, अंतर-पुस्तकालय बातचीत के लिए व्यवहार और एपीआई वादों को परिभाषित करने पर जोर दिया गया है, लेकिन यह विचार बहुत सरल है।
मैं वादों के लिए
क्यू लाइब्रेरी का उपयोग करने जा रहा हूं, क्योंकि इसमें पहले से ही प्रारंभिक जनरेटर का समर्थन है, और यह काफी परिपक्व भी है।
task.js इस विचार का प्रारंभिक कार्यान्वयन था, लेकिन इसमें वादों का एक गैर-मानक कार्यान्वयन था।
आइए एक कदम पीछे लें और एक वास्तविक जीवन उदाहरण देखें। बहुत बार हम सरल उदाहरणों का उपयोग करते हैं। यह कोड एक संदेश बनाता है, फिर उसे वापस प्राप्त करता है, और उसी टैग के साथ एक संदेश प्राप्त करता है (
client
रेडिस का एक उदाहरण है):
client.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }, function(err, res) { if(err) throw err; client.hgetall('blog::post', function(err, post) { if(err) throw err; var tags = post.tags.split(','); var posts = []; tags.forEach(function(tag) { client.hgetall('post::tag::' + tag, function(err, taggedPost) { if(err) throw err; posts.push(taggedPost); if(posts.length == tags.length) {
देखें कि यह उदाहरण कैसे बदसूरत है! कॉलबैक हमारी स्क्रीन के दाईं ओर कोड को जल्दी से दबाते हैं। इसके अलावा, सभी टैगों का अनुरोध करने के लिए, हमें प्रत्येक अनुरोध को मैन्युअल रूप से प्रबंधित करना चाहिए और जब वे सभी तैयार हों तो जांच लें।
आइए इस कोड को
Q वादों पर लाएं।
var db = { get: Q.nbind(client.get, client), set: Q.nbind(client.set, client), hmset: Q.nbind(client.hmset, client), hgetall: Q.nbind(client.hgetall, client) }; db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }).then(function() { return db.hgetall('blog::post'); }).then(function(post) { var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })).then(function(taggedPosts) {
हमें रेडिस फ़ंक्शंस को लपेटना था, और जिससे कॉलबैक-आधारित वादा-आधारित में बदल गया, यह सरल है। जैसे ही हमें वादे मिलते हैं, आप
then
कॉल करते हैं और अतुल्यकालिक संचालन के परिणाम की प्रतीक्षा करते हैं। अधिक विवरण
में वादों / A + विनिर्देशन के बारे में बताया गया
है ।
Q
कई अतिरिक्त तरीकों को लागू करता है, जैसे
all
, यह वादों की एक सरणी लेता है और उनमें से प्रत्येक को पूरा करने के लिए इंतजार करता है। इसके अलावा, वहाँ
done
जाता
done
, जो कहता है कि आपकी अतुल्यकालिक प्रक्रिया पूरी हो गई है और किसी भी तरह की अनहोनी त्रुटियों को फेंक दिया जाना चाहिए। वादों / ए + विनिर्देश के अनुसार, सभी अपवादों को त्रुटियों में परिवर्तित किया जाना चाहिए और त्रुटि हैंडलर को पारित किया जाना चाहिए। इस प्रकार, आप यह सुनिश्चित कर सकते हैं कि उन पर कोई हैंडलर न होने पर सभी त्रुटियों को फेंक दिया जाएगा। (अगर कुछ स्पष्ट नहीं है, तो कृपया डोमिनिक का
यह लेख पढ़ें ।)
ध्यान दें कि अंतिम वादा कितना गहरा है। ऐसा इसलिए है क्योंकि हमें पहले
post
करने के लिए एक्सेस की आवश्यकता है, और फिर टैग किए गए
taggedPosts
। कॉलबैक-शैली कोड यहां महसूस किया गया है, यह कष्टप्रद है।
अब जनरेटर की शक्ति का मूल्यांकन करने का समय है:
Q.async(function*() { yield db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }); var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); }));
क्या यह आश्चर्यजनक नहीं है? यह वास्तव में कैसे होता है?
Q.async
एक जनरेटर लेता है और एक फ़ंक्शन देता है जो इसे नियंत्रित करता है, जैसे कि निलंबित पुस्तकालय। हालांकि, यहां महत्वपूर्ण अंतर यह है कि जनरेटर (पैदावार) वादे देता है। क्यू हर वादा को स्वीकार करता है और उसके साथ एक जनरेटर को जोड़ता है, वादा पूरा होने पर फिर से शुरू करता है, और परिणाम वापस भेजता है।
हमें अजीब
resume
फ़ंक्शन का प्रबंधन नहीं करना है - वादे इसे पूरी तरह से संभालते हैं, और हमें
वादों के व्यवहार का लाभ मिलता है।
एक लाभ यह है कि हम आवश्यक होने पर विभिन्न Q वादों का उपयोग कर सकते हैं, उदाहरण के लिए
Q.all
, जो समानांतर में कई अतुल्यकालिक संचालन चलाता है। इस तरह आप आसानी से इसी तरह के क्यू वादों को जोड़ सकते हैं और जनरेटर में जटिल वादे कर सकते हैं ताकि जटिल निष्पादन धागे बन सकें जो बहुत साफ दिखते हैं।
यह भी ध्यान दें कि हमें घोंसले की समस्या बिल्कुल नहीं है। चूंकि
post
और
taggedPosts
एक ही दायरे में रहते हैं, इसलिए हमें अब उस दायरे की श्रृंखला को तोड़ने की चिंता नहीं करनी चाहिए, जो अविश्वसनीय रूप से मनभावन है।
त्रुटि से निपटने बहुत मुश्किल है, और आपको वास्तव में यह समझने की आवश्यकता है कि जनरेटर में उपयोग करने से पहले वादे कैसे काम करते हैं। वादों में त्रुटियां और अपवाद हमेशा त्रुटि से निपटने के लिए पारित किए जाते हैं, और वे अपवाद कभी नहीं फेंकते हैं।
कोई भी
async
जनरेटर एक वादा है, जिसमें कोई अपवाद नहीं है। आप त्रुटि कॉलबैक के साथ त्रुटियों का प्रबंधन कर सकते हैं:
someGenerator().then(null, function(err) { ... })
।
हालांकि, जनरेटर वादों का एक विशेष व्यवहार है, जो यह है कि विशेष
gen.throw
का उपयोग करके जनरेटर में फेंके गए वादों में से कोई भी त्रुटि को फेंक दिया जाएगा, जहां जनरेटर को निलंबित कर दिया गया था। इसका मतलब है कि आप जनरेटर में त्रुटियों को संभालने के लिए
try/catch
का उपयोग कर सकते हैं:
Q.async(function*() { try { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); }));
यह ठीक वैसा ही काम करता है जैसा कि आप उम्मीद करते हैं: किसी भी कॉल से
db.hgetall
त्रुटियों को
catch
हैंडलर में संसाधित किया जाएगा, भले ही वह
Q.all
भीतर एक गहन वादे में त्रुटि हो।
try/catch
बिना
try/catch
अपवाद को वादे के कॉलर के त्रुटि हैंडलर को पास किया जाएगा (यदि कोई कॉल करने वाला नहीं है, तो त्रुटि को दबा दिया जाएगा)।
इसके बारे में सोचो -
हम एसिंक्रोनस कोड के लिए कोशिश / पकड़ के साथ अपवाद हैंडलर स्थापित कर सकते हैं। त्रुटि हैंडलर का गतिशील दायरा सही होगा; कोई भी अनहोनी त्रुटियां जो ट्रायल ब्लॉक निष्पादित होने के दौरान होती हैं, उन्हें
catch
लिए पारित किया जाएगा। आप एक त्रुटि हैंडलर की उपस्थिति के बिना, यहां तक कि त्रुटियों के लिए, स्टार्टअप पर एक आश्वस्त "क्लीनअप" कोड बनाने के लिए
finally
उपयोग कर सकते हैं।
इसके अलावा, जब भी आप वादों का उपयोग करते हैं, तब उपयोग करें - इसके द्वारा आप डिफ़ॉल्ट रूप से फेंकने की त्रुटियों को चुपचाप अनदेखा कर सकते हैं, जो अक्सर अतुल्यकालिक कोड के साथ भी होता है।
Q.async
का उपयोग करने का तरीका आमतौर पर इस तरह है:
var getTaggedPosts = Q.async(function*() { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hget('blog::tag::' + tag); })); });
ऊपर लाइब्रेरी कोड है जो केवल वादे बनाता है और त्रुटि से निपटने का काम नहीं करता है। आप इसे इस तरह कहते हैं:
Q.async(function*() { var tagged = yield getTaggedPosts();
यह शीर्ष स्तर का कोड है। जैसा कि पहले उल्लेख किया गया है, किसी अपवाद के रूप में किसी भी त्रुटि के लिए एक त्रुटि को फेंकने के लिए किया जाता है। मुझे लगता है कि यह दृष्टिकोण आम है, लेकिन आपको एक अतिरिक्त विधि को कॉल करने की आवश्यकता है।
getTaggedPosts
का उपयोग वादे-जनन कार्यों द्वारा किया जाएगा। उपरोक्त कोड केवल शीर्ष-स्तरीय कोड है जो वादों से भरा है।
मैंने
पुल अनुरोध में Q.spawn का सुझाव दिया, और ये परिवर्तन पहले ही Q को हिट कर चुके हैं! इससे उन कोडों को चलाना आसान हो जाता है जो वादों का भी आसान उपयोग करते हैं:
Q.spawn(function*() { var tagged = yield getTaggedPosts();
spawn
जनरेटर प्राप्त करता है, तुरंत इसे शुरू करता है, और स्वचालित रूप से सभी अनहेल्ड त्रुटियों को फेंकता है। यह
Q.done(Q.async(function*() { ... })())
बिल्कुल बराबर है।
अन्य दृष्टिकोण
हमारा वादा-आधारित जनरेटर कोड आकार लेना शुरू कर देता है। चीनी के दाने के साथ, हम अतुल्यकालिक वर्कफ़्लो से जुड़े बहुत सारे अतिरिक्त सामान निकाल सकते हैं।
जनरेटर के साथ काम करने के कुछ समय बाद, मैंने कई तरीकों पर प्रकाश डाला।
इसके लायक नहीं है
यदि आपके पास एक छोटा कार्य है जिसे आपको केवल एक वादे की प्रतीक्षा करने की आवश्यकता है, तो जनरेटर बनाने के लिए इसके लायक नहीं है।
var getKey = Q.async(function*(key) { var x = yield r.get(dbkey(key)); return x && parseInt(x, 10); });
इस कोड का उपयोग करें:
function getKey(key) { return r.get(dbkey(key)).then(function(x) { return x && parseInt(x, 10); }); }
मुझे लगता है कि नवीनतम संस्करण क्लीनर दिखता है।
spawnMap
यह वही है जो मैंने अक्सर किया:
yield Q.all(keys.map(Q.async(function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); })));
आपके लिए एक
spawnMap
होना उपयोगी हो सकता है जो आपके लिए
Q.all(arr.map(Q.async(...)))
है।
yield spawnMap(keys, function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); })));
यह
async लाइब्रेरी से
map
विधि के समान है।
asyncCallback
आखिरी चीज जिस पर मैंने गौर किया: कई बार जब मैं एक
Q.async
फ़ंक्शन बनाना चाहता हूं और इसे सभी त्रुटियों को फेंक देता
Q.async
। यह विभिन्न पुस्तकालयों से सामान्य कॉलबैक के साथ होता है, जैसे कि एक्सप्रेस:
app.get('/url', function() { ... })
।
मैं उपरोक्त कॉलबैक को
Q.async
फ़ंक्शन में परिवर्तित नहीं कर सकता, क्योंकि तब सभी त्रुटियों को चुपचाप दबा दिया जाएगा, मैं भी
Q.spawn
उपयोग नहीं कर सकता क्योंकि यह तुरंत निष्पादित नहीं किया गया है। शायद
asyncCallback
तरह कुछ अच्छा होगा:
function asyncCallback(gen) { return function() { return Q.async(gen).apply(null, arguments).done(); }; } app.get('/project/:name', asyncCallback(function*(req, res) { var counts = yield db.getCounts(req.params.name); var post = yield db.recentPost(); res.render('project.html', { counts: counts, post: post }); }));
सारांश के रूप में
जब मैंने जनरेटर पर शोध किया, तो मुझे वास्तव में उम्मीद थी कि वे अतुल्यकालिक कोड के साथ मदद करेंगे। और, जैसा कि यह निकला, वे वास्तव में करते हैं, हालांकि आपको यह समझने की आवश्यकता है कि वादे जनरेटर के साथ प्रभावी ढंग से संयोजन करने के लिए कैसे काम करते हैं। वादे बनाने से निहितार्थ और भी अधिक निहित हो जाता है, इसलिए मैं आपको सलाह नहीं दूंगा कि जब तक आप पूरे वादे को नहीं समझ लेते तब तक आप एस्किंस या स्पॉन का उपयोग करें।
अब हमारे पास अतुल्यकालिक व्यवहार को कोड करने के लिए एक संक्षिप्त और अविश्वसनीय रूप से शक्तिशाली तरीका है और हम इसे एफएस के साथ काम करने के लिए सिर्फ संचालन करने से ज्यादा कुछ के लिए उपयोग कर सकते हैं। वास्तव में, हमारे पास संक्षिप्त, वितरित कोड लिखने का एक शानदार तरीका है जो अलग-अलग प्रोसेसर, या यहां तक कि मशीनों पर भी चल सकता है, जबकि शेष तुल्यकालिक।
लेखक से परिशिष्ट: मेरा अगला लेख पढ़िए,
ए लुक ऑन जेनर विदाउट प्रॉमिस ।