অনুবাদ সেবা

অনুবাদ সেবা

লার্নিং অনুশীলনী

ভূমিকা

Promise অবজেক্টটি একটি অ্যাসিনক্রোনাস অপারেশনের ভবিষ্যতে ঘটতে থাকা সমাপ্তি (বা ব্যর্থতা) এবং তার ফলস্বরূপ মানকে বোঝায়।

Note

এটি অনেকের জন্য কঠিন একটি বিষয়, বিশেষ করে যদি আপনি এমন কোনো ভাষায় প্রোগ্রামিং জানেন যেটি সম্পূর্ণ সিনক্রোনাস। যদি আপনি অভিভূত বোধ করেন, বা কনকারেন্সি ও প্যারালেলিজম সম্পর্কে আরও জানতে চান, তবে দুর্দান্ত টক "Concurrency is not parallelism" দেখুন (go.dev-এর মাধ্যমে) বা সরাসরি vimeo-তে দেখুন, আর স্লাইডগুলো পড়ুন।

একটি প্রমিসের লাইফসাইকেল

একটি Promise-এর তিনটি স্টেট থাকে:

  1. পেন্ডিং
  2. ফুলফিল্ড
  3. রিজেক্টেড

তৈরি হওয়ার সময় একটি প্রমিস পেন্ডিং অবস্থায় থাকে। ভবিষ্যতে কোনো এক সময়ে এটি রিজলভ বা রিজেক্ট হতে পারে। একবার কোনো প্রমিস রিজলভ বা রিজেক্ট হয়ে গেলে, তা আর কখনোই রিজলভ বা রিজেক্ট হতে পারে না, আর তার স্টেটও বদলাতে পারে না।

অন্যভাবে বললে:

  1. পেন্ডিং থাকার সময়, একটি প্রমিস:
    • ফুলফিল্ড বা রিজেক্টেড, এই দুটি স্টেটের যেকোনোটিতে রূপান্তরিত হতে পারে।
  2. ফুলফিল্ড থাকার সময়, একটি প্রমিস:
    • অন্য কোনো স্টেটে রূপান্তরিত হতে পারবে না।
    • একটি মান থাকতে হবে, যা বদলাবে না।
  3. রিজেক্টেড থাকার সময়, একটি প্রমিস:
    • অন্য কোনো স্টেটে রূপান্তরিত হতে পারবে না।
    • একটি কারণ থাকতে হবে, যা বদলাবে না।

একটি প্রমিস রিজলভ করা

একটি প্রমিস বিভিন্নভাবে রিজলভ করা যায়:

// Creates a promise that is immediately resolved
Promise.resolve(value);

// Creates a promise that is immediately resolved
new Promise((resolve) => {
  resolve(value);
});

// Chaining a promise leads to a resolved promise
somePromise.then(() => {
  // ...
  return value;
});

উপরের উদাহরণগুলোতে value যেকোনো কিছু হতে পারে, যার মধ্যে আছে একটি এরর, undefined, null বা আরেকটি প্রমিস। সাধারণত আপনি এমন একটি মান দিয়ে রিজলভ করতে চান, যেটি এরর নয়।

একটি প্রমিস রিজেক্ট করা

একটি প্রমিস বিভিন্নভাবে রিজেক্ট করা যায়:

// Creates a promise that is immediately rejected
Promise.reject(reason)

// Creates a promise that is immediately rejected
new Promise((_, reject) {
  reject(reason)
})

// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
  // ...
  throw reason
})

উপরের উদাহরণগুলোতে reason যেকোনো কিছু হতে পারে, যার মধ্যে আছে একটি এরর, undefined বা null। সাধারণত আপনি একটি এরর দিয়ে রিজেক্ট করতে চান।

একটি প্রমিস চেইন করা

কোনো প্রমিস রিজলভ বা রিজেক্ট হওয়ার পর, ভবিষ্যতের একটি অ্যাকশন দিয়ে সেটিকে চালিয়ে যাওয়া যায়।

  • promise রিজলভ হলে promise.then() কল হয়
  • promise রিজেক্ট হলে promise.catch() কল হয়
  • promise রিজলভ বা রিজেক্ট, যেকোনোটি হলে promise.finally() কল হয়

then

প্রতিটি প্রমিসই "thenable"। এর মানে হলো, একটি then ফাংশন আছে, যা আসল প্রমিস রিজলভ হওয়ার পর এক্সিকিউট হয়। promise.then(onResolved) দিলে, onResolved কলব্যাকটি আসল প্রমিস যে মান দিয়ে রিজলভ হয়েছিল সেটি পায়। এটি সবসময় একটি নতুন "চেইনড" প্রমিস রিটার্ন করে।

then থেকে একটি value রিটার্ন করলে "চেইনড" প্রমিসটি রিজলভ হয়। then-এ একটি reason থ্রো করলে "চেইনড" প্রমিসটি রিজেক্ট হয়।

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);
});

const promise2 = promise1.then(function (value) {
  console.log(value);
  // expected output: "Success!"

  return true;
});

এটি প্রায় ১০০০ ms পর "Success!" লগ করবে। promise1-এর স্টেট ও মান হবে resolved এবং "Success!"। promise2-এর স্টেট ও মান হবে resolved এবং true।

একটি দ্বিতীয় আর্গুমেন্টও আছে, যা আসল প্রমিস রিজেক্ট হলে চলে। promise.then(onResolved, onRejected) দিলে, onResolved কলব্যাকটি আসল প্রমিস যে মান দিয়ে রিজলভ হয়েছিল সেটি পায়, অথবা onRejected কলব্যাকটি প্রমিস যে কারণে রিজেক্ট হয়েছিল সেটি পায়।

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);

  if (Math.random() < 0.5) {
    reject('Nope!');
  }
});

function log(value) {
  console.log(value);
  return true;
}

function shout(reason) {
  console.error(reason.toUpperCase());
  return false;
}

const promise2 = promise1.then(log, shout);
  • প্রায় অর্ধেক ক্ষেত্রে, এটি প্রায় ১০০০ ms পর "Success!" লগ করবে।
    • promise1-এর স্টেট ও মান হবে resolved এবং "Success!"।
    • promise2-এর স্টেট ও মান হবে resolved এবং true।
  • প্রায় অর্ধেক ক্ষেত্রে, এটি সাথে সাথে "NOPE!" লগ করবে।
    • promise1-এর স্টেট ও মান হবে rejected এবং Nope!।
    • promise2-এর স্টেট ও মান হবে resolved এবং false।

এটা বোঝা জরুরি যে লাইফসাইকেলের নিয়মের কারণে, যখন এটি reject করে, তখন প্রায় ১০০০ms পর আসা resolve-টি চুপচাপ উপেক্ষা করা হয়, কারণ একবার রিজেক্ট বা রিজলভ হয়ে গেলে ভেতরের স্টেট আর বদলাতে পারে না। এটাও বোঝা জরুরি যে একটি প্রমিস থেকে মান রিটার্ন করলে সেটি রিজলভ হয়, আর কোনো মান থ্রো করলে সেটি রিজেক্ট হয়। promise1 রিজলভ হলে এবং একটি চেইনড onResolved থাকলে: then(onResolved), তখন সেই পরবর্তীটি একটি নতুন প্রমিস, যা রিজলভ বা রিজেক্ট হতে পারে। promise1 রিজেক্ট হলে কিন্তু একটি চেইনড onRejected থাকলে: then(, onRejected), তখন সেই পরবর্তীটি একটি নতুন প্রমিস, যা রিজলভ বা রিজেক্ট হতে পারে।

catch

কখনো কখনো আপনি এরর ধরে রাখতে চান এবং শুধু তখনই এগিয়ে যেতে চান, যখন আসল প্রমিস reject করে। promise.catch(onCatch) দিলে, onCatch কলব্যাকটি আসল প্রমিস যে কারণে রিজেক্ট হয়েছিল সেটি পায়। এটি সবসময় একটি নতুন "চেইনড" প্রমিস রিটার্ন করে।

catch থেকে একটি value রিটার্ন করলে "চেইনড" প্রমিসটি রিজলভ হয়। catch-এ একটি reason থ্রো করলে "চেইনড" প্রমিসটি রিজেক্ট হয়।

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);

  if (Math.random() < 0.5) {
    reject('Nope!');
  }
});

function log(value) {
  console.log(value);
  return 'done';
}

function recover(reason) {
  console.error(reason.toUpperCase());
  return 42;
}

const promise2 = promise1.catch(recover).then(log);

প্রায় অর্ধেক ক্ষেত্রে, এটি প্রায় ১০০০ ms পর "Success!" লগ করবে। বাকি অর্ধেক ক্ষেত্রে, এটি সাথে সাথে 42 লগ করবে।

  • promise1 রিজলভ হলে, catch বাদ পড়ে এবং এটি then-এ পৌঁছায়, আর মানটি লগ করে।
    • promise1-এর স্টেট ও মান হবে resolved এবং "Success!"।
    • promise2-এর স্টেট ও মান হবে resolved এবং "done";
  • promise1 রিজেক্ট হলে, catch এক্সিকিউট হয়, যা একটি মান রিটার্ন করে, ফলে চেইনটি এখন resolved, এবং এটি then-এ পৌঁছায়, আর মানটি লগ করে।
    • promise1-এর স্টেট ও মান হবে rejected এবং "Nope!"।
    • promise2-এর স্টেট ও মান হবে resolved এবং "done";

finally

কখনো কখনো আপনি একটি প্রমিস সেটল হওয়ার পর কোড এক্সিকিউট করতে চান, প্রমিসটি রিজলভ করুক বা রিজেক্ট করুক। promise.finally(onSettled) দিলে, onSettled কলব্যাকটি কিছুই পায় না। এটি সবসময় একটি নতুন "চেইনড" প্রমিস রিটার্ন করে।

finally থেকে একটি value রিটার্ন করলে আসল প্রমিসের স্টেটাস ও মান কপি হয়, value-টি উপেক্ষা করে। finally-এ একটি reason থ্রো করলে "চেইনড" প্রমিসটি রিজেক্ট হয়, আর আসল প্রমিসের যেকোনো স্টেটাস ও মান বা কারণ মুছে যায়।

উদাহরণ

বিভিন্ন মেথড একসাথে:

const myPromise = new Promise(function (resolve, reject) {
  const sampleData = [2, 4, 6, 8];
  const randomNumber = Math.round(Math.random() * 5);

  if (sampleData[randomNumber]) {
    resolve(sampleData[randomNumber]);
  } else {
    reject('Sampling did not result in a sample');
  }
});

const finalPromise = myPromise
  .then(function (sampled) {
    // If the random number was 0, 1, 2, or 3, this will be
    // reached and the number 2, 4, 6, or 8 will be logged.
    console.log(`Sampled data: ${sampled}`);
    return 'yay';
  })
  .catch(function (reason) {
    // If the random number was 4 or 5, this will be reached and
    // reason will be "An error occurred". The entire chain will
    // then reject with an Error with the reason as message.
    throw new Error(reason);
  })
  .finally(function () {
    // This will always log after either the sampled data is
    // logged or the error is raised.
    console.log('Promise completed');
  });
  • যে ক্ষেত্রে randomNumber হলো 0-3:
    • myPromise মান 2, 4, 6, or 8 দিয়ে রিজলভ হবে
    • finalPromise মান 'yay' দিয়ে রিজলভ হবে
    • দুটি লগ থাকবে:
      • Sampled data: ...
      • Promise completed
  • যে ক্ষেত্রে randomNumber হলো 4-5:
    • myPromise কারণ 'Sampling did not result in a sample' দিয়ে রিজেক্ট হবে
    • finalPromise কারণ Error('Sampling did not result in a sample') দিয়ে রিজেক্ট হবে
    • একটি লগ থাকবে:
      • Promise completed
      • কিছু এনভায়রনমেন্টে এটি একটি "uncaught rejected promise: Error('Sampling did not result in a sample')" লগ তৈরি করবে

উপরের উদাহরণে দেখা যাচ্ছে, reject একটি স্ট্রিং দিয়েও কাজ করে, এবং একটি প্রমিস Error দিয়েও রিজেক্ট হতে পারে।

Note

প্রমিস চেইন করা বা সাধারণভাবে এগুলো ব্যবহার করা পরিষ্কার না হলে, MDN-এর টিউটোরিয়ালটি পড়ে দেখার জন্য ভালো একটি রিসোর্স।

নির্দেশনা

এই অনুশীলনীতে আপনি একটি TranslationService তৈরি করবেন, যা ফ্রি সদস্যদের জন্য সাধারণ অনুবাদ সেবা এবং কোয়ালিটির নিশ্চয়তাসহ প্রিমিয়াম সদস্যদের জন্য উন্নত অনুবাদ প্রদান করে।

API

আপনি মহাকাশে একটি অনুবাদ API খুঁজে পেয়েছেন, যা যেকোনো অনুবাদ request যুক্তিসঙ্গত সময়ের মধ্যে পূরণ করে। আপনি এর সুবিধা নিতে চান। মহাকাশের অনুবাদকরা অত্যন্ত খামখেয়ালী এবং পুনরাবৃত্তি অপছন্দ করেন, তাই তারা API স্টোরেজ স্যাটেলাইটও দেন, যেখান থেকে আপনি তাদের বিরক্ত না করেই আগের অনুবাদগুলো fetch করতে পারেন।

একটি অনুবাদ ফেচ করা

api.fetch(text) API স্টোরেজ থেকে text-এর একটি অনুবাদ ফেচ করে এবং একটি promise রিটার্ন করে, যা দুটি মান দেয়:

  • translation: প্রকৃত অনুবাদ
  • quality: একটি সংখ্যায় প্রকাশ করা কোয়ালিটি

API স্টোরেজ-এ কোনো অনুবাদ না পাওয়া গেলে API একটি NotAvailable এরর ছোড়ে। api.request মেথড ব্যবহার করে অনুবাদ যোগ করা যায়। 'text' অনুবাদযোগ্য না হলে API একটি Untranslatable এরর ছোড়ে।

api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })

একটি অনুবাদ রিকোয়েস্ট করা

কিছু অনুবাদ নিশ্চিতভাবেই আছে, কিন্তু সেগুলো এখনো API স্টোরেজ-এ যোগ করা হয়নি। এটাই NotAvailable (স্টোরেজে নেই, তবে রিকোয়েস্ট করা যায়) আর Untranslatable (অনুবাদ করা যায় না)-এর মধ্যে পার্থক্য।

api.request(text, callback) অনুরোধ করে যে text-এর একটি অনুবাদ সম্পন্ন করে API স্টোরেজ-এ যোগ করা হোক। কাজ শেষ হলে callback ফাংশনটি কল করা হয়।

  • সফল হলে callback-এ undefined পাঠানো হয়: এটি বোঝায় অনুবাদটি সফল হয়েছে এবং api.fetch মেথড দিয়ে তা পাওয়া যাবে।
  • ব্যর্থ হলে callback-এ একটি error পাঠানো হয়: এটি বোঝায় কোথাও কিছু ভুল হয়েছে। আউটার স্পেস API-টি অস্থির, অর্থাৎ API প্রায়ই ব্যর্থ হয়। এমন হলে আবার api.request করলে অসুবিধা নেই।
api.request('majQa’', callback);
// => undefined
//
// later: the passed callback is called with undefined
//        because it was successful.

⚠ সতর্কতা! ⚠

Caution

কোনো request এলেই API বিভিন্ন অনুবাদককে টেলিপোর্ট করে এনে নিজের জাদু দেখায়। এটি খুবই ব্যয়বহুল কাজ, তাই কোনো অনুবাদ আছে এমন অবস্থায় এটি কল করা উচিত নয়। দুর্ভাগ্যবশত, সবাই ম্যানুয়াল পড়ে না, তাই খারাপ ব্যবহারকারীদের বাদ দেওয়ার একটি ব্যবস্থা রাখা হয়েছে।

text আগে থেকেই পাওয়া থাকা সত্ত্বেও যদি api.request কল করা হয়, তবে API এই কলের জন্য একটি AbusiveClientError ছোড়ে, এবং তার পরের প্রতিটি কলের জন্যও। কোনো কিছু আগেই অনূদিত হয়ে থাকলে আপনি যেন কখনোই অনুবাদ রিকোয়েস্ট না করেন, তা নিশ্চিত করুন।

1. কোয়ালিটি উপেক্ষা করে একটি অনুবাদ ফেচ করুন

ফ্রি সেবাটি কেবল সেই অনুবাদই দেয়, যেগুলো এই মুহূর্তে API স্টোরেজ-এ আছে।

এমন একটি মেথড free(text) ইমপ্লিমেন্ট করুন, যা ফ্রি সদস্যদের API স্টোরেজ-এ আগে থেকেই থাকা অনুবাদ দেয়। কোয়ালিটি উপেক্ষা করুন এবং API-এর ছোড়া যেকোনো এরর ফরওয়ার্ড করুন।

  • অনুবাদটি আনা সম্ভব হলে, তার কোয়ালিটি যাই হোক না কেন, সেটি রিটার্ন করে
  • অনুবাদ API থেকে আসা যেকোনো এরর ফরওয়ার্ড করে
  • api.fetch মেথড ব্যবহার করে (api.fetch একটি promise রিটার্ন করে)
service.free('jIyaj');
// => Promise<...> resolves "I understand."

service.free("jIyajbe'");
// => Promise<...> rejects Error("Not yet translated")

2. একগুচ্ছ অনুবাদ একবারে ফেচ করুন, সব নয়তো কিছুই নয়

ফ্রি সদস্যদের জন্য একটি মেথড batch([text, text, ...]) ইমপ্লিমেন্ট করুন, যা ফ্রি সেবা ব্যবহার করে টেক্সটের একটি অ্যারে অনুবাদ করে এবং সব অনুবাদ অথবা একটি এরর রিটার্ন করে।

  • সবগুলো পাওয়া গেলে সব অনুবাদ (একই ক্রমে) রিজলভ করে
  • যে এররটি প্রথমে আসে, সেটি দিয়ে রিজেক্ট করে
  • কোনো টেক্সট না দিলে BatchIsEmpty এরর দিয়ে রিজেক্ট করে
service.batch(['jIyaj', "majQa'"]);
// => Promise<...> resolves ["I understand.", "Well done!"]

service.batch(['jIyaj', "jIyajbe'"]);
// => Promise<...> rejects new Error("Not yet translated")

service.batch([]);
// => Promise<...> rejects BatchIsEmpty()

3. একটি অনুবাদ রিকোয়েস্ট করুন, সর্বোচ্চ ২ বার রিট্রাই করে

একটি প্রিমিয়াম ইউজার মেথড request(text) ইমপ্লিমেন্ট করুন, যা API স্টোরেজ-এ একটি অনুবাদ যোগ করার রিকোয়েস্ট করে। কোনো ব্যর্থতা ঘটলে রিকোয়েস্টটি স্বয়ংক্রিয়ভাবে আবার চেষ্টা করবে। একই রিকোয়েস্টের জন্য এটি ৩ বারের বেশি কল করবে না (মহাকাশের অনুবাদকদের রাগাবেন না!!!)।

  • api.request কোনো এরর না দিলে undefined দিয়ে রিজলভ করে
  • api.request একটি এরর দিলে সর্বোচ্চ দুইবার রিট্রাই করে
  • রিট্রাই শেষ হয়ে গেলে সর্বশেষ পাওয়া এরর দিয়ে রিজেক্ট করে
service.request("jIyajbe'");
// => Promise<...> resolves (with nothing), can now be retrieved using the fetch API

4. একটি অনুবাদ ফেচ করুন, কোয়ালিটি যাচাই করুন, নয়তো রিকোয়েস্ট করুন

একটি অনুবাদ ফেচ করার জন্য প্রিমিয়াম ইউজার মেথড premium(text, quality) ইমপ্লিমেন্ট করুন। কোনো অনুবাদ NotAvailable হলে, অনুবাদটি রিকোয়েস্ট করুন এবং সেটি API স্টোরেজ-এ যোগ হওয়ার পর ফেচ করুন। মেথডটি কেবল তখনই অনুবাদ রিটার্ন করবে, যখন তা একটি নির্দিষ্ট quality থ্রেশহোল্ড পূরণ করে।

  • api.fetch রিজলভ করলে, রিজলভ করার আগে কোয়ালিটি যাচাই করুন
  • api.fetch রিজেক্ট করলে, বরং অনুবাদটি রিকোয়েস্ট করুন
  • api.request রিজেক্ট করলে, এররটি ফরওয়ার্ড করুন
service.premium("jIyajbe'", 100);
// => Promise<...> resolves "I don't understand."

service.premium("'arlogh Qoylu'pu'?", 100);
// => Promise<...> rejects QualityThresholdNotMet()

service.premium("'arlogh Qoylu'pu'?", 40);
// => Promise<...> resolves "What time is it?"

দ্রষ্টব্য

Note

'arlogh Qoylu'pu'?-এর সঠিক অনুবাদ হলো How many times has it been heard?।

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
JavaScript Exercism

অনুবাদ সেবা শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, JavaScript ট্র্যাকের 37টি কনসেপ্ট159টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।