জাভাস্ক্রিপ্ট প্রতিশ্রুতি: একটি ভূমিকা

প্রতিশ্রুতি বিলম্বিত এবং অ্যাসিঙ্ক্রোনাস গণনাকে সহজ করে। একটি প্রতিশ্রুতি এমন একটি অপারেশনকে প্রতিনিধিত্ব করে যা এখনও সম্পূর্ণ হয়নি৷

বিকাশকারীরা, ওয়েব ডেভেলপমেন্টের ইতিহাসে একটি গুরুত্বপূর্ণ মুহূর্তের জন্য নিজেকে প্রস্তুত করুন।

[ড্রামরোল শুরু হয়]

জাভাস্ক্রিপ্টে প্রতিশ্রুতি এসেছে!

[আতশবাজি বিস্ফোরিত, উপর থেকে চকচকে কাগজের বৃষ্টি, ভিড় বন্য হয়ে যায়]

এই মুহুর্তে আপনি এই বিভাগগুলির মধ্যে একটিতে পড়েন:

  • লোকেরা আপনার চারপাশে উল্লাস করছে, কিন্তু আপনি নিশ্চিত নন যে সমস্ত হট্টগোল কী। হয়তো আপনি একটি "প্রতিশ্রুতি" কি তা নিশ্চিত নন। আপনি কাঁধে উঠবেন, কিন্তু চকচকে কাগজের ওজন আপনার কাঁধে ভাসছে। যদি তাই হয়, এটা নিয়ে চিন্তা করবেন না, আমার কেন এই জিনিসের যত্ন নেওয়া উচিত তা নির্ধারণ করতে আমার বয়স লেগেছে। আপনি সম্ভবত শুরুতে শুরু করতে চান।
  • আপনি বাতাসে ঘুষি! ঠিক সময় সম্পর্কে? আপনি আগে এই প্রতিশ্রুতি জিনিসগুলি ব্যবহার করেছেন কিন্তু এটি আপনাকে বিরক্ত করে যে সমস্ত বাস্তবায়নের একটি সামান্য ভিন্ন API আছে। অফিসিয়াল জাভাস্ক্রিপ্ট সংস্করণের জন্য API কি? আপনি সম্ভবত পরিভাষা দিয়ে শুরু করতে চান।
  • আপনি এই সম্পর্কে ইতিমধ্যেই জানতেন এবং আপনি তাদের উপহাস করছেন যারা লাফিয়ে লাফিয়ে উঠছে তাদের কাছে এটি একটি খবর। আপনার নিজের শ্রেষ্ঠত্বের জন্য কিছুক্ষণ সময় নিন, তারপর সরাসরি API রেফারেন্সে যান।

ব্রাউজার সমর্থন এবং পলিফিল

ব্রাউজার সমর্থন

  • 32
  • 12
  • 29
  • 8

উৎস

যে সকল ব্রাউজারে সম্পূর্ণ প্রতিশ্রুতি বাস্তবায়নের অভাব রয়েছে সেগুলিকে বিশেষ সম্মতি পর্যন্ত আনতে বা অন্যান্য ব্রাউজার এবং Node.js-এ প্রতিশ্রুতি যুক্ত করতে, পলিফিল (2k gzipped) দেখুন।

কি নিয়ে এত হৈচৈ?

জাভাস্ক্রিপ্ট একক থ্রেডেড, যার অর্থ স্ক্রিপ্টের দুটি বিট একই সময়ে চলতে পারে না; তাদের একের পর এক দৌড়াতে হবে। ব্রাউজারে, জাভাস্ক্রিপ্ট একটি থ্রেড শেয়ার করে অন্যান্য স্টাফের সাথে যা ব্রাউজার থেকে ব্রাউজারে আলাদা। কিন্তু সাধারণত জাভাস্ক্রিপ্ট পেইন্টিং, শৈলী আপডেট করা এবং ব্যবহারকারীর ক্রিয়াগুলি পরিচালনা করার মতো একই সারিতে থাকে (যেমন পাঠ্য হাইলাইট করা এবং ফর্ম নিয়ন্ত্রণের সাথে ইন্টারঅ্যাক্ট করা)। এই জিনিসগুলির একটিতে কার্যকলাপ অন্যটিকে বিলম্বিত করে।

একজন মানুষ হিসাবে, আপনি মাল্টিথ্রেডেড। আপনি একাধিক আঙ্গুল দিয়ে টাইপ করতে পারেন, আপনি একই সময়ে একটি কথোপকথন চালাতে এবং ধরে রাখতে পারেন। আমাদের একমাত্র ব্লকিং ফাংশনটি হ'ল হাঁচি, যেখানে সমস্ত বর্তমান কার্যকলাপ অবশ্যই হাঁচির সময়কালের জন্য স্থগিত করা উচিত। এটি বেশ বিরক্তিকর, বিশেষ করে যখন আপনি গাড়ি চালাচ্ছেন এবং কথোপকথন করার চেষ্টা করছেন। আপনি sneezy যে কোড লিখতে চান না.

আপনি সম্ভবত এটি কাছাকাছি পেতে ইভেন্ট এবং কলব্যাক ব্যবহার করেছেন. এখানে ইভেন্ট আছে:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // woo yey image loaded
});

img1.addEventListener('error', function() {
  // argh everything's broken
});

এটি মোটেও হাঁচি নয়। আমরা ইমেজ পাই, কিছু শ্রোতা যোগ করি, তারপর জাভাস্ক্রিপ্ট কার্যকর করা বন্ধ করতে পারে যতক্ষণ না সেই শ্রোতাদের একজনকে ডাকা হয়।

দুর্ভাগ্যবশত, উপরের উদাহরণে, এটা সম্ভব যে ঘটনাগুলি আমরা শোনা শুরু করার আগে ঘটেছিল, তাই আমাদেরকে চিত্রগুলির "সম্পূর্ণ" বৈশিষ্ট্য ব্যবহার করে এটিকে ঘিরে কাজ করতে হবে:

var img1 = document.querySelector('.img-1');

function loaded() {
  // woo yey image loaded
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // argh everything's broken
});

আমরা তাদের জন্য শোনার সুযোগ পাওয়ার আগে ত্রুটিযুক্ত চিত্রগুলি ধরতে পারে না; দুর্ভাগ্যবশত DOM আমাদের এটি করার একটি উপায় দেয় না। এছাড়াও, এটি একটি ছবি লোড হচ্ছে। বিষয়গুলি আরও জটিল হয়ে ওঠে যদি আমরা জানতে চাই কখন ছবির একটি সেট লোড হয়েছে৷

ইভেন্ট সবসময় সেরা উপায় হয় না

ইভেন্টগুলি এমন জিনিসগুলির জন্য দুর্দান্ত যা একই বস্তুতে একাধিকবার ঘটতে পারে— keyup , touchstart ইত্যাদি৷ সেই ইভেন্টগুলির সাথে আপনি শ্রোতাকে সংযুক্ত করার আগে কী ঘটেছিল তা নিয়ে আপনি সত্যিই চিন্তা করেন না৷ কিন্তু যখন এটি অ্যাসিঙ্ক সাফল্য/ব্যর্থতার কথা আসে, আদর্শভাবে আপনি এইরকম কিছু চান:

img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});

// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});

এটা কি প্রতিশ্রুতি, কিন্তু ভাল নামকরণ সঙ্গে. যদি এইচটিএমএল চিত্র উপাদানগুলির একটি "প্রস্তুত" পদ্ধতি থাকে যা একটি প্রতিশ্রুতি প্রদান করে, আমরা এটি করতে পারি:

img1.ready()
.then(function() {
  // loaded
}, function() {
  // failed
});

// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
  // all loaded
}, function() {
  // one or more failed
});

তাদের সবচেয়ে মৌলিকভাবে, প্রতিশ্রুতিগুলি ইভেন্টের শ্রোতাদের মতো একটু ব্যতীত:

  • একটি প্রতিশ্রুতি শুধুমাত্র একবার সফল বা ব্যর্থ হতে পারে। এটি দুবার সফল বা ব্যর্থ হতে পারে না, এটি সাফল্য থেকে ব্যর্থতায় বা তদ্বিপরীত হতে পারে না।
  • যদি কোনো প্রতিশ্রুতি সফল বা ব্যর্থ হয় এবং আপনি পরে একটি সফল/ব্যর্থতার কলব্যাক যোগ করেন, সঠিক কলব্যাক বলা হবে, যদিও ঘটনাটি আগে ঘটেছিল।

এটি অ্যাসিঙ্ক সাফল্য/ব্যর্থতার জন্য অত্যন্ত উপযোগী, কারণ আপনি সঠিক সময়ে কিছু উপলব্ধ হওয়ার বিষয়ে কম আগ্রহী এবং ফলাফলে প্রতিক্রিয়া জানাতে বেশি আগ্রহী।

প্রতিশ্রুতি পরিভাষা

ডোমেনিক ডেনিকোলা প্রুফ এই নিবন্ধের প্রথম খসড়াটি পড়ে এবং পরিভাষার জন্য আমাকে "F" গ্রেড করেছে। তিনি আমাকে আটকে রেখেছিলেন, আমাকে 100 বার রাজ্য এবং ভাগ্য অনুলিপি করতে বাধ্য করেছিলেন এবং আমার বাবা-মাকে একটি উদ্বেগজনক চিঠি লিখেছিলেন। তা সত্ত্বেও, আমি এখনও অনেক পরিভাষা মিশ্রিত করেছি, তবে এখানে মূল বিষয়গুলি রয়েছে:

একটি প্রতিশ্রুতি হতে পারে:

  • পরিপূর্ণ - প্রতিশ্রুতি সংক্রান্ত কর্ম সফল হয়েছে
  • প্রত্যাখ্যান - প্রতিশ্রুতি সংক্রান্ত পদক্ষেপ ব্যর্থ হয়েছে
  • মুলতুবি - এখনও পূরণ বা প্রত্যাখ্যান করা হয়নি
  • নিষ্পত্তি হয়েছে - পূরণ বা প্রত্যাখ্যান করেছে

স্পেকটি প্রতিশ্রুতির মতো একটি বস্তুকে বর্ণনা করার জন্য তারপরেযোগ্য শব্দটিও ব্যবহার করে, যেখানে এটির একটি then পদ্ধতি রয়েছে। এই শব্দটি আমাকে প্রাক্তন ইংল্যান্ড ফুটবল ম্যানেজার টেরি ভেনাবলসের কথা মনে করিয়ে দেয় তাই আমি এটি যতটা সম্ভব কম ব্যবহার করব।

প্রতিশ্রুতি জাভাস্ক্রিপ্টে আসে!

প্রতিশ্রুতিগুলি লাইব্রেরির আকারে কিছু সময়ের জন্য রয়েছে, যেমন:

উপরের এবং জাভাস্ক্রিপ্টের প্রতিশ্রুতিগুলি প্রতিশ্রুতি/A+ নামে একটি সাধারণ, প্রমিত আচরণ ভাগ করে। আপনি যদি একজন jQuery ব্যবহারকারী হন, তাদের কাছে Deferreds নামে অনুরূপ কিছু আছে। যাইহোক, বিলম্বিতগুলি প্রতিশ্রুতি/A+ অনুগত নয়, যা তাদের সূক্ষ্মভাবে আলাদা এবং কম দরকারী করে তোলে, তাই সাবধান। jQuery-এরও একটি প্রতিশ্রুতি প্রকার রয়েছে, কিন্তু এটি Deferred-এর একটি উপসেট এবং একই সমস্যা রয়েছে।

যদিও প্রতিশ্রুতি বাস্তবায়ন একটি প্রমিত আচরণ অনুসরণ করে, তাদের সামগ্রিক APIগুলি ভিন্ন। জাভাস্ক্রিপ্টের প্রতিশ্রুতি RSVP.js-এর মত API-এ। আপনি কীভাবে একটি প্রতিশ্রুতি তৈরি করবেন তা এখানে:

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

প্রতিশ্রুতি কনস্ট্রাক্টর একটি যুক্তি নেয়, দুটি প্যারামিটার সহ একটি কলব্যাক, সমাধান এবং প্রত্যাখ্যান। কলব্যাকের মধ্যে কিছু করুন, সম্ভবত অ্যাসিঙ্ক করুন, তারপর সবকিছু কাজ করলে কল সমাধান করুন, অন্যথায় কল প্রত্যাখ্যান করুন।

সাধারণ পুরানো জাভাস্ক্রিপ্টে throw মতো, এটি একটি ত্রুটি বস্তুর সাথে প্রত্যাখ্যান করার জন্য প্রথাগত, কিন্তু প্রয়োজনীয় নয়। ত্রুটি বস্তুর সুবিধা হল তারা একটি স্ট্যাক ট্রেস ক্যাপচার করে, যা ডিবাগিং সরঞ্জামগুলিকে আরও সহায়ক করে তোলে।

আপনি এই প্রতিশ্রুতিটি কীভাবে ব্যবহার করেন তা এখানে:

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

then() দুটি আর্গুমেন্ট নেয়, একটি সফল মামলার জন্য একটি কলব্যাক এবং আরেকটি ব্যর্থতার ক্ষেত্রে। উভয়ই ঐচ্ছিক, তাই আপনি শুধুমাত্র সাফল্য বা ব্যর্থতার ক্ষেত্রে একটি কলব্যাক যোগ করতে পারেন।

জাভাস্ক্রিপ্ট প্রতিশ্রুতিগুলি DOM-এ "ফিউচার" হিসাবে শুরু হয়েছিল, যার নাম পরিবর্তন করে "প্রতিশ্রুতি" রাখা হয়েছিল এবং অবশেষে জাভাস্ক্রিপ্টে স্থানান্তরিত হয়েছিল। DOM-এর পরিবর্তে জাভাস্ক্রিপ্টে এগুলি থাকা দুর্দান্ত কারণ সেগুলি নন-ব্রাউজার JS প্রসঙ্গ যেমন Node.js-এ উপলব্ধ হবে (তারা তাদের মূল APIগুলিতে সেগুলি ব্যবহার করবে কিনা তা অন্য প্রশ্ন)৷

যদিও তারা একটি জাভাস্ক্রিপ্ট বৈশিষ্ট্য, DOM তাদের ব্যবহার করতে ভয় পায় না। প্রকৃতপক্ষে, অ্যাসিঙ্ক সাফল্য/ব্যর্থতার পদ্ধতি সহ সমস্ত নতুন DOM API প্রতিশ্রুতি ব্যবহার করবে। কোটা ম্যানেজমেন্ট , ফন্ট লোড ইভেন্টস , সার্ভিসওয়ার্কার , ওয়েব MIDI , স্ট্রীম এবং আরও অনেক কিছুর সাথে এটি ইতিমধ্যেই ঘটছে৷

অন্যান্য লাইব্রেরির সাথে সামঞ্জস্য

জাভাস্ক্রিপ্ট প্রতিশ্রুতি দেয় যে API একটি then() পদ্ধতির সাথে যেকোন কিছুকে প্রতিশ্রুতির মতো (অথবা প্রতিশ্রুতি-বলা দীর্ঘশ্বাসে তারপরে thenable ) হিসাবে বিবেচনা করবে, তাই আপনি যদি একটি লাইব্রেরি ব্যবহার করেন যা একটি Q প্রতিশ্রুতি প্রদান করে, তবে এটি ঠিক আছে, এটি নতুনের সাথে ভাল খেলবে জাভাস্ক্রিপ্ট প্রতিশ্রুতি.

যদিও, যেমন আমি উল্লেখ করেছি, jQuery এর Deferreds কিছুটা … অসহায়। সৌভাগ্যক্রমে আপনি তাদের আদর্শ প্রতিশ্রুতিতে নিক্ষেপ করতে পারেন, যা যত তাড়াতাড়ি সম্ভব করা মূল্যবান:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))

এখানে, jQuery এর $.ajax একটি Deferred প্রদান করে। যেহেতু এটির একটি then() পদ্ধতি রয়েছে, Promise.resolve() এটিকে জাভাস্ক্রিপ্ট প্রতিশ্রুতিতে পরিণত করতে পারে। যাইহোক, কখনও কখনও বিলম্বিতরা তাদের কলব্যাকে একাধিক আর্গুমেন্ট পাস করে, উদাহরণস্বরূপ:

var jqDeferred = $.ajax('/whatever.json');

jqDeferred.then(function(response, statusText, xhrObj) {
  // ...
}, function(xhrObj, textStatus, err) {
  // ...
})

যদিও জেএস প্রতিশ্রুতি প্রথমটি ছাড়া সব উপেক্ষা করে:

jsPromise.then(function(response) {
  // ...
}, function(xhrObj) {
  // ...
})

সৌভাগ্যক্রমে এটি সাধারণত আপনি যা চান তা হয়, বা অন্তত আপনি যা চান তাতে অ্যাক্সেস দেয়। এছাড়াও, সচেতন থাকুন যে jQuery ত্রুটি বস্তুগুলিকে প্রত্যাখ্যানে পাস করার নিয়ম অনুসরণ করে না।

জটিল অ্যাসিঙ্ক কোড সহজ করে দিয়েছে

ডান, এর কিছু জিনিস কোড. বলুন আমরা চাই:

  1. লোডিং নির্দেশ করতে একটি স্পিনার শুরু করুন
  2. একটি গল্পের জন্য কিছু JSON আনুন, যা আমাদের শিরোনাম দেয়, এবং প্রতিটি অধ্যায়ের জন্য ইউআরএল
  3. পাতায় শিরোনাম যোগ করুন
  4. প্রতিটি অধ্যায় আনুন
  5. পৃষ্ঠায় গল্প যোগ করুন
  6. স্পিনার থামান

… তবে পথের মধ্যে কিছু ভুল হলে ব্যবহারকারীকেও বলুন। আমরা সেই সময়েও স্পিনারটিকে থামাতে চাই, অন্যথায় এটি ঘুরতে থাকবে, মাথা ঘোরাবে এবং অন্য কোনও UI-তে ক্র্যাশ হবে।

অবশ্যই, আপনি একটি গল্প সরবরাহ করতে জাভাস্ক্রিপ্ট ব্যবহার করবেন না, এইচটিএমএল হিসাবে পরিবেশন করা দ্রুততর , কিন্তু APIগুলির সাথে ডিল করার সময় এই প্যাটার্নটি বেশ সাধারণ: একাধিক ডেটা নিয়ে আসে, তারপর সবকিছু হয়ে গেলে কিছু করুন৷

শুরু করার জন্য, নেটওয়ার্ক থেকে ডেটা আনার বিষয়ে কাজ করা যাক:

প্রতিশ্রুতিশীল XMLHttpRequest

পুরানো APIগুলি প্রতিশ্রুতি ব্যবহার করার জন্য আপডেট করা হবে, যদি এটি একটি পিছনের সামঞ্জস্যপূর্ণ উপায়ে সম্ভব হয়। XMLHttpRequest একটি প্রধান প্রার্থী, কিন্তু এর মধ্যে একটি GET অনুরোধ করার জন্য একটি সাধারণ ফাংশন লিখুন:

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

এখন এটি ব্যবহার করা যাক:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

এখন আমরা ম্যানুয়ালি XMLHttpRequest টাইপ না করেই HTTP অনুরোধ করতে পারি, যা দুর্দান্ত, কারণ আমাকে XMLHttpRequest এর বিরক্তিকর উট-কেসিং যত কম দেখতে হবে, আমার জীবন তত সুখী হবে।

চেইনিং

then() গল্পের শেষ নয়, আপনি মান রূপান্তর করতে বা একের পর এক অতিরিক্ত অ্যাসিঙ্ক অ্যাকশন চালাতে then চেইন করতে পারেন।

মান পরিবর্তন

আপনি নতুন মান ফিরিয়ে দিয়ে মানগুলিকে রূপান্তর করতে পারেন:

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
})

একটি ব্যবহারিক উদাহরণ হিসাবে, আসুন ফিরে যাই:

get('story.json').then(function(response) {
  console.log("Success!", response);
})

প্রতিক্রিয়া হল JSON, কিন্তু আমরা বর্তমানে এটিকে প্লেইন টেক্সট হিসেবে পাচ্ছি। আমরা JSON responseType ব্যবহার করার জন্য আমাদের get ফাংশন পরিবর্তন করতে পারি, কিন্তু আমরা প্রতিশ্রুতি ভূমিতেও এটি সমাধান করতে পারি:

get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})

যেহেতু JSON.parse() একটি একক যুক্তি নেয় এবং একটি রূপান্তরিত মান প্রদান করে, আমরা একটি শর্টকাট করতে পারি:

get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})

আসলে, আমরা একটি getJSON() ফাংশন সত্যিই সহজেই তৈরি করতে পারি:

function getJSON(url) {
  return get(url).then(JSON.parse);
}

getJSON() এখনও একটি প্রতিশ্রুতি ফেরত দেয়, যেটি একটি url নিয়ে আসে তারপর প্রতিক্রিয়াটিকে JSON হিসাবে পার্স করে।

সারিবদ্ধ অ্যাসিঙ্ক্রোনাস অ্যাকশন

আপনি ক্রমানুসারে অ্যাসিঙ্ক অ্যাকশন চালানোর জন্য then s চেইন করতে পারেন।

আপনি যখন একটি then() কলব্যাক থেকে কিছু ফেরত দেন, এটি একটি বিট জাদু। যদি আপনি একটি মান ফেরত দেন, তাহলে পরবর্তী then() টিকে সেই মান দিয়ে ডাকা হয়। যাইহোক, আপনি যদি প্রতিশ্রুতির মতো কিছু ফেরত দেন, তাহলে পরবর্তী then() এটির জন্য অপেক্ষা করে এবং শুধুমাত্র তখনই বলা হয় যখন সেই প্রতিশ্রুতি স্থির হয় (সফল/ব্যর্থ হয়)। উদাহরণ স্বরূপ:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
})

এখানে আমরা story.json এ একটি async অনুরোধ করি, যা আমাদের অনুরোধ করার জন্য URL-এর একটি সেট দেয়, তারপর আমরা তাদের মধ্যে প্রথমটির জন্য অনুরোধ করি। প্রতিশ্রুতি সত্যিই সাধারণ কলব্যাক নিদর্শন থেকে আলাদা হতে শুরু যখন এই হয়.

আপনি অধ্যায় পেতে একটি শর্টকাট পদ্ধতিও তৈরি করতে পারেন:

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
})

getChapter কল না হওয়া পর্যন্ত আমরা story.json ডাউনলোড করি না, কিন্তু পরের বার (গুলি) getChapter বলা হলে আমরা গল্পের প্রতিশ্রুতি পুনরায় ব্যবহার করি, তাই story.json শুধুমাত্র একবার আনা হয়। ইয়া প্রতিশ্রুতি!

ত্রুটি পরিচালনা

যেমনটি আমরা আগে দেখেছি, then() দুটি আর্গুমেন্ট নেয়, একটি সাফল্যের জন্য, একটি ব্যর্থতার জন্য (বা প্রতিশ্রুতি-কথায় পূরণ করুন এবং প্রত্যাখ্যান করুন):

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
})

এছাড়াও আপনি catch() ব্যবহার করতে পারেন:

get('story.json').then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
})

catch() সম্পর্কে বিশেষ কিছু নেই, এটি শুধুমাত্র then(undefined, func) , কিন্তু এটি আরও পঠনযোগ্য। মনে রাখবেন যে উপরের দুটি কোড উদাহরণ একই আচরণ করে না, পরবর্তীটি এর সমতুল্য:

get('story.json').then(function(response) {
  console.log("Success!", response);
}).then(undefined, function(error) {
  console.log("Failed!", error);
})

পার্থক্য সূক্ষ্ম, কিন্তু অত্যন্ত দরকারী. প্রতিশ্রুতি প্রত্যাখ্যানগুলি একটি প্রত্যাখ্যান কলব্যাক (অথবা catch() then() এগিয়ে যান, যেহেতু এটি সমতুল্য)। then(func1, func2) , func1 বা func2 বলা হবে, উভয়ই নয়। কিন্তু then(func1).catch(func2) এর সাথে, func1 প্রত্যাখ্যান করলে উভয়কেই বলা হবে, কারণ এগুলি চেইনের পৃথক ধাপ। নিম্নলিখিত নিন:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

উপরের প্রবাহটি সাধারণ জাভাস্ক্রিপ্ট ট্রাই/ক্যাচের অনুরূপ, একটি "ট্রাই" এর মধ্যে যে ত্রুটিগুলি ঘটে তা অবিলম্বে catch() ব্লকে চলে যায়। এখানে একটি ফ্লোচার্ট হিসাবে উপরেরটি রয়েছে (কারণ আমি ফ্লোচার্ট পছন্দ করি):

প্রতিশ্রুতি পূরণের জন্য নীল রেখা অনুসরণ করুন, বা প্রত্যাখ্যানকারীদের জন্য লাল রেখা অনুসরণ করুন।

জাভাস্ক্রিপ্ট ব্যতিক্রম এবং প্রতিশ্রুতি

প্রত্যাখ্যান ঘটবে যখন একটি প্রতিশ্রুতি স্পষ্টভাবে প্রত্যাখ্যান করা হয়, তবে কনস্ট্রাক্টর কলব্যাকে একটি ত্রুটি নিক্ষিপ্ত হলে নিহিতভাবেও:

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain't JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

এর মানে হল প্রতিশ্রুতি কনস্ট্রাক্টর কলব্যাকের ভিতরে আপনার সমস্ত প্রতিশ্রুতি-সম্পর্কিত কাজ করা দরকারী, তাই ত্রুটিগুলি স্বয়ংক্রিয়ভাবে ধরা পড়ে এবং প্রত্যাখ্যান হয়ে যায়।

then() কলব্যাকগুলিতে নিক্ষিপ্ত ত্রুটিগুলির ক্ষেত্রেও এটি একই।

get('/').then(JSON.parse).then(function() {
  // This never happens, '/' is an HTML page, not JSON
  // so JSON.parse throws
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

অনুশীলনে ত্রুটি হ্যান্ডলিং

আমাদের গল্প এবং অধ্যায়গুলির সাথে, আমরা ব্যবহারকারীর কাছে একটি ত্রুটি প্রদর্শন করতে ক্যাচ ব্যবহার করতে পারি:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Failed to show chapter");
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

যদি story.chapterUrls[0] আনয়ন ব্যর্থ হয় (যেমন, http 500 বা ব্যবহারকারী অফলাইন), এটি নিম্নলিখিত সমস্ত সফল কলব্যাকগুলি এড়িয়ে যাবে, যার মধ্যে getJSON() এর একটি অন্তর্ভুক্ত যা প্রতিক্রিয়াটিকে JSON হিসাবে পার্স করার চেষ্টা করে এবং এছাড়াও এড়িয়ে যায় কলব্যাক যা পৃষ্ঠায় অধ্যায়1.html যোগ করে। পরিবর্তে এটি ক্যাচ কলব্যাকের দিকে চলে যায়। ফলস্বরূপ, "অধ্যায় দেখাতে ব্যর্থ" যদি পূর্ববর্তী কোনো কাজ ব্যর্থ হয় তাহলে পৃষ্ঠায় যোগ করা হবে।

জাভাস্ক্রিপ্টের চেষ্টা/ক্যাচের মতো, ত্রুটি ধরা পড়ে এবং পরবর্তী কোড চলতে থাকে, তাই স্পিনার সবসময় লুকানো থাকে, যা আমরা চাই। উপরেরটি এর একটি নন-ব্লকিং অ্যাসিঙ্ক সংস্করণ হয়ে ওঠে:

try {
  var story = getJSONSync('story.json');
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'

আপনি ত্রুটি থেকে পুনরুদ্ধার না করেই কেবল লগিংয়ের উদ্দেশ্যে catch() করতে চাইতে পারেন। এটি করতে, শুধু ত্রুটিটি পুনরায় থ্রো করুন। আমরা আমাদের getJSON() পদ্ধতিতে এটি করতে পারি:

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("getJSON failed for", url, err);
    throw err;
  });
}

তাই আমরা একটি অধ্যায় আনতে পরিচালিত করেছি, কিন্তু আমরা তাদের সব চাই। চলুন যে ঘটতে.

সমান্তরালতা এবং সিকোয়েন্সিং: উভয়ের সেরা পাওয়া

অ্যাসিঙ্ক ভাবা সহজ নয়। আপনি যদি চিহ্নটি বন্ধ করতে সংগ্রাম করছেন, কোডটি এমনভাবে লেখার চেষ্টা করুন যেন এটি সিঙ্ক্রোনাস। এক্ষেত্রে:

try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector('.spinner').style.display = 'none'

ওই কাজগুলো! কিন্তু জিনিসগুলি ডাউনলোড করার সময় এটি সিঙ্ক এবং ব্রাউজারটিকে লক করে দেয়৷ এই কাজটি অ্যাসিঙ্ক করার জন্য আমরা then() ব্যবহার করি যাতে একের পর এক জিনিস ঘটে।

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

কিন্তু কিভাবে আমরা অধ্যায় url মাধ্যমে লুপ এবং ক্রম তাদের আনয়ন করতে পারেন? এটি কাজ করে না :

story.chapterUrls.forEach(function(chapterUrl) {
  // Fetch chapter
  getJSON(chapterUrl).then(function(chapter) {
    // and add it to the page
    addHtmlToPage(chapter.html);
  });
})

forEach অ্যাসিঙ্ক-সচেতন নয়, তাই আমাদের অধ্যায়গুলি তারা যে ক্রমে ডাউনলোড করবে তাতে প্রদর্শিত হবে, যা মূলত পাল্প ফিকশন কীভাবে লেখা হয়েছিল। এটি পাল্প ফিকশন নয়, তাই আসুন এটি ঠিক করি।

একটি ক্রম তৈরি

আমরা আমাদের chapterUrls অ্যারেকে প্রতিশ্রুতির একটি ক্রমতে পরিণত করতে চাই। then() ব্যবহার করে আমরা এটি করতে পারি:

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
  // Add these actions to the end of the sequence
  sequence = sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
})

এই প্রথম আমরা Promise.resolve() দেখেছি, যা একটি প্রতিশ্রুতি তৈরি করে যা আপনি এটিকে যে মূল্য দেন তা সমাধান করে। আপনি যদি এটিকে Promise একটি উদাহরণ পাস করেন তবে এটি কেবল এটিকে ফিরিয়ে দেবে ( দ্রষ্টব্য: এটি এমন একটি পরিবর্তন যা কিছু বাস্তবায়ন এখনও অনুসরণ করে না)। আপনি যদি এটিকে প্রতিশ্রুতির মতো কিছু পাস করেন (একটি then() পদ্ধতি আছে), এটি একটি প্রকৃত Promise তৈরি করে যা একইভাবে পূরণ/প্রত্যাখ্যান করে। আপনি যদি অন্য কোনো মান পাস করেন, যেমন, Promise.resolve('Hello') , এটি একটি প্রতিশ্রুতি তৈরি করে যা সেই মানটির সাথে পূরণ করে। যদি আপনি এটিকে কোন মান ছাড়াই কল করেন, উপরের মত, এটি "অনির্ধারিত" দিয়ে পূরণ করে।

এছাড়াও আছে Promise.reject(val) , যা একটি প্রতিশ্রুতি তৈরি করে যা আপনার দেওয়া মান দিয়ে প্রত্যাখ্যান করে (বা অনির্ধারিত)।

আমরা array.reduce ব্যবহার করে উপরের কোডটি পরিপাটি করতে পারি:

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // Add these actions to the end of the sequence
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve())

এটি আগের উদাহরণের মতোই করছে, তবে আলাদা "ক্রম" ভেরিয়েবলের প্রয়োজন নেই। অ্যারের প্রতিটি আইটেমের জন্য আমাদের হ্রাস কলব্যাক বলা হয়। "সিকোয়েন্স" হল Promise.resolve() প্রথমবার, কিন্তু বাকি কলগুলির জন্য "sequence" হল যা আমরা আগের কল থেকে ফিরে এসেছি। array.reduce একটি অ্যারেকে একটি একক মান পর্যন্ত ফুটিয়ে তোলার জন্য সত্যিই দরকারী, যা এই ক্ষেত্রে একটি প্রতিশ্রুতি।

আসুন এটি সব একসাথে করা যাক:

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter's promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

এবং সেখানে আমাদের এটি আছে, সিঙ্ক সংস্করণের একটি সম্পূর্ণরূপে অ্যাসিঙ্ক সংস্করণ। কিন্তু আমরা আরো ভালো করতে পারে। এই মুহুর্তে আমাদের পৃষ্ঠাটি এইরকম ডাউনলোড হচ্ছে:

ব্রাউজারগুলি একসাথে একাধিক জিনিস ডাউনলোড করতে বেশ ভাল, তাই আমরা একের পর এক অধ্যায় ডাউনলোড করে কার্যক্ষমতা হারাচ্ছি। আমরা যা করতে চাই তা হল সেগুলিকে একই সময়ে ডাউনলোড করুন, তারপরে যখন তারা পৌঁছে যাবে তখন সেগুলিকে প্রক্রিয়া করুন৷ সৌভাগ্যক্রমে এর জন্য একটি API আছে:

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  //...
})

Promise.all প্রতিশ্রুতির একটি অ্যারে নেয় এবং একটি প্রতিশ্রুতি তৈরি করে যা সফলভাবে সম্পূর্ণ হলে তা পূরণ হয়। আপনি পাস করা প্রতিশ্রুতির মতো একই ক্রমে ফলাফলের একটি বিন্যাস (প্রতিশ্রুতি পূরণ করা যাই হোক না কেন) পান।

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // Take an array of promises and wait on them all
  return Promise.all(
    // Map our array of chapter urls to
    // an array of chapter json promises
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  // Now we have the chapters jsons in order! Loop through…
  chapters.forEach(function(chapter) {
    // …and add to the page
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened so far
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

সংযোগের উপর নির্ভর করে, এটি একের পর এক লোড হওয়ার চেয়ে কয়েক সেকেন্ড দ্রুত হতে পারে এবং এটি আমাদের প্রথম চেষ্টার চেয়ে কম কোড। অধ্যায়গুলি যে কোনও ক্রমে ডাউনলোড করতে পারে, তবে সেগুলি সঠিক ক্রমে পর্দায় উপস্থিত হয়।

যাইহোক, আমরা এখনও অনুভূত কর্মক্ষমতা উন্নত করতে পারেন. প্রথম অধ্যায় এলে আমাদের এটিকে পৃষ্ঠায় যুক্ত করা উচিত। এটি ব্যবহারকারীকে বাকি অধ্যায়গুলি আসার আগে পড়া শুরু করতে দেয়। যখন তৃতীয় অধ্যায় আসবে, আমরা এটিকে পৃষ্ঠায় যুক্ত করব না কারণ ব্যবহারকারী বুঝতে পারে না যে অধ্যায় দুটি অনুপস্থিত। যখন অধ্যায় দুই আসে, আমরা অধ্যায় দুই এবং তিন যোগ করতে পারি, ইত্যাদি ইত্যাদি।

এটি করার জন্য, আমরা একই সময়ে আমাদের সমস্ত অধ্যায়ের জন্য JSON নিয়ে আসি, তারপর সেগুলি নথিতে যুক্ত করার জন্য একটি ক্রম তৈরি করি:

getJSON('story.json')
.then(function(story) {
  addHtmlToPage(story.heading);

  // Map our array of chapter urls to
  // an array of chapter json promises.
  // This makes sure they all download in parallel.
  return story.chapterUrls.map(getJSON)
    .reduce(function(sequence, chapterPromise) {
      // Use reduce to chain the promises together,
      // adding content to the page for each chapter
      return sequence
      .then(function() {
        // Wait for everything in the sequence so far,
        // then wait for this chapter to arrive.
        return chapterPromise;
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
}).then(function() {
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

এবং সেখানে আমরা যাই, উভয়ের সেরা! সমস্ত বিষয়বস্তু সরবরাহ করতে একই পরিমাণ সময় লাগে, তবে ব্যবহারকারী প্রথম বিট সামগ্রীটি তাড়াতাড়ি পায়।

এই তুচ্ছ উদাহরণে, সমস্ত অধ্যায় একই সময়ে আসে, কিন্তু একবারে একটি প্রদর্শন করার সুবিধাটি আরও, বড় অধ্যায়গুলির সাথে অতিরঞ্জিত হবে।

Node.js-স্টাইল কলব্যাক বা ইভেন্টগুলির সাথে উপরেরটি করা কোডের প্রায় দ্বিগুণ, তবে আরও গুরুত্বপূর্ণভাবে অনুসরণ করা ততটা সহজ নয়। যাইহোক, এটি প্রতিশ্রুতির গল্পের শেষ নয়, অন্যান্য ES6 বৈশিষ্ট্যগুলির সাথে মিলিত হলে তারা আরও সহজ হয়ে যায়।

বোনাস রাউন্ড: প্রসারিত ক্ষমতা

যেহেতু আমি মূলত এই নিবন্ধটি লিখেছি, তাই প্রতিশ্রুতি ব্যবহার করার ক্ষমতা ব্যাপকভাবে প্রসারিত হয়েছে। ক্রোম 55 থেকে, অ্যাসিঙ্ক ফাংশনগুলি প্রতিশ্রুতি-ভিত্তিক কোড লেখার অনুমতি দিয়েছে যেন এটি সিঙ্ক্রোনাস, কিন্তু মূল থ্রেড ব্লক না করে। আপনি আমার async ফাংশন নিবন্ধে এটি সম্পর্কে আরও পড়তে পারেন। প্রধান ব্রাউজারগুলিতে প্রতিশ্রুতি এবং অ্যাসিঙ্ক ফাংশন উভয়ের জন্যই ব্যাপক সমর্থন রয়েছে। আপনি MDN এর প্রতিশ্রুতি এবং async ফাংশন রেফারেন্সে বিশদটি খুঁজে পেতে পারেন।

অ্যান ভ্যান কেস্টেরেন, ডোমেনিক ডেনিকোলা, টম অ্যাশওয়ার্থ, রেমি শার্প, অ্যাডি ওসমানি, আর্থার ইভান্স, এবং ইউটাকা হিরানোকে অনেক ধন্যবাদ যারা এটি প্রমাণ করেছেন এবং সংশোধন/সুপারিশ করেছেন।

এছাড়াও, নিবন্ধের বিভিন্ন অংশ আপডেট করার জন্য ম্যাথিয়াস বাইনেন্সকে ধন্যবাদ।