JavaScript چیست؟ آموزش کامل جاوا اسکریپت با ساخت دستیار هوش مصنوعی

در این آموزش JavaScript را از صفر تا سطح کاربردی یاد می‌گیرید؛ از متغیر، تابع، آرایه و Object تا DOM، Promise، Async/Await و Fetch API. در پایان یک دستیار هوش مصنوعی واقعی با JavaScript، Node.js و API درواره می‌سازیم.

Share
JavaScript چیست؟ آموزش کامل جاوا اسکریپت با ساخت دستیار هوش مصنوعی

JavaScript یا جاوا اسکریپت یکی از مهم‌ترین زبان‌های برنامه‌نویسی جهان و زبان اصلی ایجاد رفتارهای تعاملی در صفحات وب است. تقریباً هر زمان که بدون بارگذاری مجدد صفحه فرمی ارسال می‌شود، منویی باز می‌شود، اطلاعاتی از API دریافت می‌شود یا یک رابط کاربری تغییر می‌کند، JavaScript در اجرای آن نقش دارد.

اما کاربرد JavaScript به مرورگر محدود نیست. با استفاده از Node.js می‌توان از جاوا اسکریپت برای توسعه Backend، ساخت API، ابزارهای خط فرمان، اپلیکیشن دسکتاپ، برنامه موبایل، افزونه مرورگر و سرویس‌های مبتنی بر هوش مصنوعی استفاده کرد.

در این آموزش، ابتدا مفاهیم اصلی JavaScript را از صفر یاد می‌گیریم و سپس یک پروژه واقعی می‌سازیم: یک دستیار هوش مصنوعی فارسی که رابط آن در مرورگر اجرا می‌شود، Backend آن با Node.js نوشته شده و از طریق API درواره به مدل انتخابی متصل می‌شود.

JavaScript چیست؟

JavaScript یک زبان برنامه‌نویسی سطح بالا، پویا و چندمنظوره است. این زبان ابتدا برای ایجاد تعامل در صفحات وب طراحی شد، اما امروزه در مرورگر، سرور، اپلیکیشن موبایل، نرم‌افزار دسکتاپ و بسیاری از محیط‌های دیگر اجرا می‌شود.

در یک وب‌سایت معمولاً سه فناوری اصلی کنار یکدیگر قرار می‌گیرند:

فناوریوظیفه اصلی
HTMLساختار و معنای محتوا
CSSظاهر، رنگ و چیدمان
JavaScriptمنطق، رفتار و تعامل

برای مثال:

<button id="hello-button">کلیک کنید</button>
<p id="result"></p>

JavaScript می‌تواند به کلیک کاربر واکنش نشان دهد:

const button = document.querySelector("#hello-button");
const result = document.querySelector("#result");

button.addEventListener("click", () => {
  result.textContent = "سلام از JavaScript!";
});

براساس راهنمای رسمی JavaScript در MDN، مباحث اصلی این زبان شامل انواع داده، متغیرها، کنترل جریان، حلقه‌ها، توابع، آرایه‌ها، Objectها، Classها، Promiseها و Moduleها است.

تفاوت JavaScript و Java

JavaScript و Java دو زبان متفاوت هستند و نباید به‌دلیل شباهت اسمی با یکدیگر اشتباه گرفته شوند.

ویژگیJavaScriptJava
نوع زبانپویا و مبتنی بر Prototypeایستا و مبتنی بر Class
کاربرد مشهوروب، Node.js و رابط کاربریBackend، سازمانی و Android قدیمی
اجرای رایجمرورگر یا Runtime مانند Node.jsJVM
نوع‌دهیDynamic TypingStatic Typing
فایل متداول.js.java

یادگیری Java برای شروع JavaScript لازم نیست.

ECMAScript چیست؟

ECMAScript استانداردی است که رفتار و ویژگی‌های اصلی زبان JavaScript را تعریف می‌کند. JavaScript یکی از پیاده‌سازی‌های این استاندارد است.

قابلیت‌هایی مانند موارد زیر در نسخه‌های مختلف استاندارد ECMAScript توسعه یافته‌اند:

  • let و const
  • Arrow Function
  • Class
  • Promise
  • Async/Await
  • Module
  • Optional Chaining
  • Nullish Coalescing
  • Destructuring

در پروژه‌های جدید بهتر است از JavaScript مدرن استفاده کنید و سازگاری ویژگی‌ها با مرورگرها یا Runtime هدف را بررسی کنید.

JavaScript کجا اجرا می‌شود؟

اجرا در مرورگر

مرورگرهایی مانند Chrome، Firefox، Edge و Safari دارای موتور JavaScript هستند. کد JavaScript مرورگر می‌تواند با صفحه، رویدادهای کاربر و Web APIها تعامل داشته باشد.

اجرا در Node.js

Node.js محیطی برای اجرای JavaScript خارج از مرورگر است. با Node.js می‌توان:

  • وب‌سرور ساخت.
  • به دیتابیس متصل شد.
  • فایل خواند و نوشت.
  • API ایجاد کرد.
  • درخواست HTTP ارسال کرد.
  • Worker و Job پردازشی اجرا کرد.
  • ابزار خط فرمان ساخت.

محیط‌های دیگر

JavaScript در محیط‌های دیگری نیز استفاده می‌شود:

  • افزونه‌های مرورگر
  • اپلیکیشن دسکتاپ با Electron
  • اپلیکیشن موبایل با React Native
  • Serverless Function
  • Edge Runtime
  • ابزارهای Build
  • تست خودکار مرورگر
  • اپلیکیشن‌های تلویزیون هوشمند

چگونه JavaScript را به HTML اضافه کنیم؟

JavaScript داخلی

<script>
  console.log("Hello JavaScript");
</script>

این روش برای مثال کوچک قابل‌استفاده است، اما در پروژه واقعی بهتر است کد در فایل جداگانه قرار گیرد.

فایل JavaScript خارجی

<script src="./app.js" defer></script>

ویژگی defer باعث می‌شود فایل بدون متوقف‌کردن پردازش HTML دریافت شود و پس از آماده‌شدن ساختار صفحه اجرا شود.

JavaScript Module

<script type="module" src="./main.js"></script>

در Module می‌توان از import و export استفاده کرد:

import { sendMessage } from "./api.js";

Moduleها به تقسیم کد، استفاده مجدد و نگهداری بهتر پروژه کمک می‌کنند. جزئیات آن‌ها در راهنمای JavaScript Modules در MDN آمده است.

اولین برنامه JavaScript

یک فایل با نام app.js بسازید:

const userName = "امیر";

console.log(`سلام ${userName}`);

برای اجرای آن در مرورگر، فایل را به HTML متصل کنید. برای اجرا با Node.js:

node app.js

خروجی:

سلام امیر

کامنت در JavaScript

کامنت تک‌خطی:

// این یک کامنت تک‌خطی است
const modelName = "YOUR_MODEL_ID";

کامنت چندخطی:

/*
  این بخش تنظیمات
  برنامه را نگهداری می‌کند.
*/

کامنت باید دلیل تصمیم مهم را توضیح دهد، نه اینکه کد واضح را دوباره به زبان طبیعی تکرار کند.

متغیر در JavaScript

برای تعریف متغیر معمولاً از const و let استفاده می‌شود.

const

اگر قرار نیست متغیر دوباره مقداردهی شود:

const apiBaseUrl = "/api";
const maxMessageLength = 4000;

let

اگر مقدار متغیر در ادامه تغییر می‌کند:

let isLoading = false;

isLoading = true;

var

var روش قدیمی‌تری برای تعریف متغیر است و به‌دلیل Scope و Hoisting متفاوت، در کدهای جدید معمولاً const و let ترجیح داده می‌شوند.

var oldStyleVariable = "value";

قاعده عملی:

  • ابتدا از const استفاده کنید.
  • فقط زمانی که مقدار باید تغییر کند از let استفاده کنید.
  • در کد جدید معمولاً از var استفاده نکنید.

انواع داده در JavaScript

JavaScript دارای چند نوع داده اصلی است.

String

const message = "سلام";
const modelId = "YOUR_MODEL_ID";

Number

const temperature = 0.4;
const maxTokens = 1000;

در JavaScript بیشتر عددهای معمولی از نوع number هستند.

BigInt

برای عددهای صحیح بسیار بزرگ:

const largeNumber = 9007199254740993n;

Boolean

const isConnected = true;
const hasError = false;

Undefined

let response;

console.log(response); // undefined

Null

const selectedConversation = null;

null معمولاً نشان می‌دهد نبودن مقدار به‌صورت آگاهانه تعریف شده است.

Symbol

Symbol برای ساخت شناسه‌های یکتا و بعضی الگوهای پیشرفته زبان استفاده می‌شود:

const internalId = Symbol("internal-id");

Object

const model = {
  id: "YOUR_MODEL_ID",
  temperature: 0.4,
  streaming: false
};

آرایه، تابع و بسیاری از ساختارهای JavaScript نیز از نظر فنی با سیستم Object زبان ارتباط دارند.

Dynamic Typing چیست؟

JavaScript دارای نوع‌دهی پویا است. یعنی نوع متغیر براساس مقدار آن تعیین می‌شود:

let value = "متن";
value = 42;
value = true;

این انعطاف می‌تواند توسعه سریع را ساده کند، اما در پروژه‌های بزرگ ممکن است باعث خطاهای Runtime شود.

TypeScript با اضافه‌کردن بررسی نوع در زمان توسعه، بخشی از این مشکلات را کاهش می‌دهد. بااین‌حال، برای یادگیری TypeScript باید ابتدا مفاهیم اصلی JavaScript را بشناسید.

بررسی نوع داده با typeof

console.log(typeof "hello"); // string
console.log(typeof 42); // number
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof {}); // object
console.log(typeof []); // object

برای بررسی آرایه از روش زیر استفاده کنید:

const messages = [];

console.log(Array.isArray(messages)); // true

یکی از رفتارهای قدیمی زبان:

console.log(typeof null); // object

برای بررسی null مستقیماً مقایسه کنید:

if (value === null) {
  console.log("مقدار null است");
}

عملگرهای مهم

عملگرهای محاسباتی

const total = 10 + 5;
const difference = 10 - 5;
const product = 10 * 5;
const division = 10 / 5;
const remainder = 10 % 3;
const power = 2 ** 3;

مقایسه

console.log(10 > 5);
console.log(10 >= 10);
console.log(5 < 10);
console.log(5 <= 5);

برابری سخت‌گیرانه

console.log(5 === 5); // true
console.log(5 === "5"); // false

بهتر است در بیشتر موارد از === و !== استفاده کنید:

if (statusCode !== 200) {
  console.log("پاسخ موفق نبود");
}

عملگر == تبدیل نوع ضمنی انجام می‌دهد و ممکن است نتیجه غیرمنتظره ایجاد کند:

console.log(5 == "5"); // true

عملگرهای منطقی

const canSend =
  isConnected && !isLoading;

const displayName =
  userName || "کاربر";

const retryCount =
  options.retryCount ?? 2;

تفاوت || و ?? مهم است. عملگر || همه مقادیر Falsy را رد می‌کند، اما ?? فقط در صورت null یا undefined به مقدار جایگزین می‌رسد.

const temperature = 0;

console.log(temperature || 0.7); // 0.7
console.log(temperature ?? 0.7); // 0

Template Literal

Template Literal با Backtick نوشته می‌شود:

const name = "کاربر";
const message = `سلام ${name}`;

متن چندخطی:

const systemPrompt = `
شما یک دستیار فارسی هستید.
پاسخ را دقیق و روشن ارائه کنید.
`;

شرط در JavaScript

if و else

const messageLength = 120;

if (messageLength === 0) {
  console.log("پیام خالی است");
} else if (messageLength > 4000) {
  console.log("پیام بیش از حد طولانی است");
} else {
  console.log("پیام معتبر است");
}

عملگر سه‌تایی

const label =
  isLoading ? "در حال ارسال..." : "ارسال";

عملگر سه‌تایی برای تصمیم‌های کوتاه مناسب است. برای منطق پیچیده، if خواناتر خواهد بود.

switch

function getStatusMessage(statusCode) {
  switch (statusCode) {
    case 200:
      return "درخواست موفق بود";

    case 401:
      return "دسترسی تأیید نشد";

    case 429:
      return "تعداد درخواست‌ها بیش از حد مجاز است";

    default:
      return "خطای غیرمنتظره";
  }
}

Truthy و Falsy

مقادیر زیر Falsy هستند:

false
0
-0
0n
""
null
undefined
NaN

مثال:

const prompt = "";

if (!prompt) {
  console.log("پرامپت خالی است");
}

اما مراقب باشید مقدار صفر ممکن است معتبر باشد:

const temperature = 0;

if (temperature === undefined) {
  console.log("Temperature مشخص نشده است");
}

حلقه‌ها

for

for (let index = 0; index < 3; index += 1) {
  console.log(index);
}

for...of

برای پیمایش مقدارهای Array:

const messages = [
  "سلام",
  "JavaScript چیست؟",
  "یک مثال بده"
];

for (const message of messages) {
  console.log(message);
}

for...in

برای پیمایش کلیدهای Object استفاده می‌شود:

const settings = {
  temperature: 0.4,
  maxTokens: 1000
};

for (const key in settings) {
  console.log(key, settings[key]);
}

برای Array معمولاً از for...in استفاده نکنید.

while

let attempt = 0;

while (attempt < 3) {
  attempt += 1;
  console.log(`تلاش شماره ${attempt}`);
}

تابع در JavaScript

تابع بخشی از منطق را به واحدی قابل‌استفاده مجدد تبدیل می‌کند.

Function Declaration

function normalizePrompt(prompt) {
  return prompt.trim();
}

Function Expression

const normalizePrompt = function (prompt) {
  return prompt.trim();
};

Arrow Function

const normalizePrompt = (prompt) => {
  return prompt.trim();
};

نسخه کوتاه:

const normalizePrompt = (prompt) => prompt.trim();

پارامتر پیش‌فرض

function createMessage(
  content,
  role = "user"
) {
  return {
    role,
    content
  };
}

Rest Parameter

function combineMessages(...messages) {
  return messages.join("\n");
}

const result = combineMessages(
  "پیام اول",
  "پیام دوم",
  "پیام سوم"
);

Scope چیست؟

Scope مشخص می‌کند یک متغیر در کدام بخش برنامه قابل‌دسترسی است.

const appName = "Darvareh Assistant";

function startApp() {
  const status = "ready";

  console.log(appName);
  console.log(status);
}

startApp();

// status در اینجا قابل دسترسی نیست.

متغیرهای تعریف‌شده با let و const دارای Block Scope هستند:

if (true) {
  const localValue = 10;
  console.log(localValue);
}

// localValue خارج از Block وجود ندارد.

Closure چیست؟

Closure زمانی شکل می‌گیرد که یک تابع به متغیرهای Scope بیرونی خود دسترسی را حفظ کند.

function createCounter() {
  let count = 0;

  return function increment() {
    count += 1;
    return count;
  };
}

const nextRequestId = createCounter();

console.log(nextRequestId()); // 1
console.log(nextRequestId()); // 2

Closure در Event Handler، Factory Function و نگهداری State خصوصی کاربرد دارد.

Array در JavaScript

Array مجموعه‌ای مرتب از مقادیر است:

const messages = [
  {
    role: "user",
    content: "سلام"
  },
  {
    role: "assistant",
    content: "سلام! چگونه کمک کنم؟"
  }
];

اضافه‌کردن مقدار

messages.push({
  role: "user",
  content: "JavaScript چیست؟"
});

حذف آخرین مقدار

const lastMessage = messages.pop();

map

برای تبدیل هر عضو:

const contents = messages.map(
  (message) => message.content
);

filter

برای انتخاب بعضی اعضا:

const userMessages = messages.filter(
  (message) => message.role === "user"
);

find

برای یافتن اولین عضو مطابق شرط:

const firstAssistantMessage = messages.find(
  (message) => message.role === "assistant"
);

some

const hasSystemMessage = messages.some(
  (message) => message.role === "system"
);

every

const allMessagesAreValid = messages.every(
  (message) =>
    typeof message.content === "string" &&
    message.content.trim().length > 0
);

reduce

const totalCharacters = messages.reduce(
  (total, message) =>
    total + message.content.length,
  0
);

متدهای map و filter آرایه جدید می‌سازند، اما push آرایه اصلی را تغییر می‌دهد.

Object در JavaScript

Object اطلاعات مرتبط را به‌صورت کلید و مقدار نگهداری می‌کند:

const requestOptions = {
  model: "YOUR_MODEL_ID",
  temperature: 0.4,
  maxTokens: 1200
};

دسترسی به Property:

console.log(requestOptions.model);
console.log(requestOptions["temperature"]);

تغییر مقدار:

requestOptions.temperature = 0.2;

Destructuring

const {
  model,
  temperature,
  maxTokens
} = requestOptions;

Object Shorthand

const model = "YOUR_MODEL_ID";
const temperature = 0.4;

const request = {
  model,
  temperature
};

Spread Syntax

const defaultOptions = {
  temperature: 0.4,
  maxTokens: 1200
};

const customOptions = {
  ...defaultOptions,
  temperature: 0.2
};

Spread یک کپی سطحی ایجاد می‌کند. Objectهای تو‌در‌تو همچنان ممکن است Reference مشترک داشته باشند.

Optional Chaining

برای دسترسی ایمن‌تر به Property تو‌در‌تو:

const content =
  response?.choices?.[0]?.message?.content;

اگر یکی از بخش‌های مسیر null یا undefined باشد، نتیجه undefined خواهد بود.

می‌توان آن را با Nullish Coalescing ترکیب کرد:

const content =
  response?.choices?.[0]?.message?.content
  ?? "پاسخی دریافت نشد";

این روش جای اعتبارسنجی کامل پاسخ API را نمی‌گیرد، اما از بعضی خطاهای دسترسی جلوگیری می‌کند.

JSON چیست؟

JSON قالبی متنی برای تبادل داده است.

Object جاوا اسکریپت:

const message = {
  role: "user",
  content: "سلام"
};

تبدیل به JSON:

const json = JSON.stringify(message);

خروجی:

{"role":"user","content":"سلام"}

تبدیل JSON به مقدار JavaScript:

const parsed = JSON.parse(json);

JSON.parse ممکن است برای ورودی نامعتبر خطا ایجاد کند:

try {
  const data = JSON.parse(input);
  console.log(data);
} catch (error) {
  console.error("JSON معتبر نیست");
}

DOM چیست؟

DOM مخفف Document Object Model است. مرورگر HTML را به ساختاری از Objectها تبدیل می‌کند تا JavaScript بتواند عناصر صفحه را بخواند و تغییر دهد.

انتخاب عنصر

const form = document.querySelector("#chat-form");
const input = document.querySelector("#prompt");

انتخاب چند عنصر:

const buttons =
  document.querySelectorAll(".suggestion");

تغییر متن

const status = document.querySelector("#status");

status.textContent = "آماده";

تغییر Class

status.classList.add("status--loading");
status.classList.remove("status--loading");
status.classList.toggle("status--active");

ساخت عنصر

const article = document.createElement("article");
article.className = "message";
article.textContent = "پیام جدید";

document
  .querySelector("#message-list")
  .append(article);

استفاده ایمن از textContent

برای قراردادن متن دریافت‌شده از کاربر یا مدل، textContent انتخاب امن‌تری است:

messageElement.textContent = modelResponse;

قرار دادن مستقیم ورودی خارجی در innerHTML می‌تواند باعث تفسیر آن به‌عنوان HTML شود:

// برای محتوای کنترل‌نشده مناسب نیست
messageElement.innerHTML = modelResponse;

اگر به نمایش Markdown نیاز دارید، از Parser معتبر استفاده کنید و HTML تولیدشده را قبل از نمایش پاک‌سازی یا Sanitize کنید.

Event در JavaScript

Event رویدادی مانند کلیک، تایپ یا ارسال فرم است.

const button = document.querySelector("#send-button");

button.addEventListener("click", () => {
  console.log("دکمه کلیک شد");
});

ارسال فرم:

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const prompt = input.value.trim();

  if (!prompt) {
    return;
  }

  console.log(prompt);
});

preventDefault() رفتار پیش‌فرض فرم، یعنی بارگذاری مجدد صفحه، را متوقف می‌کند.

Event Delegation

اگر تعداد زیادی آیتم پویا دارید، می‌توانید یک Listener روی والد قرار دهید:

const messageList =
  document.querySelector("#message-list");

messageList.addEventListener("click", (event) => {
  const copyButton =
    event.target.closest("[data-copy-message]");

  if (!copyButton) {
    return;
  }

  const messageId = copyButton.dataset.copyMessage;

  console.log("Copy:", messageId);
});

این روش برای فهرست پیام‌هایی که بعداً به DOM اضافه می‌شوند مناسب است.

هم‌زمانی و Event Loop

JavaScript معمولاً کد عادی را روی یک Thread اصلی اجرا می‌کند، اما عملیات‌هایی مانند درخواست شبکه و Timer از طریق محیط اجرا مدیریت می‌شوند.

مثال:

console.log("اول");

setTimeout(() => {
  console.log("سوم");
}, 0);

console.log("دوم");

خروجی:

اول
دوم
سوم

حتی با زمان صفر، Callback پس از پایان اجرای کد هم‌زمان فعلی وارد صف اجرا می‌شود.

برای توسعه کاربردی لازم نیست در ابتدا تمام جزئیات Event Loop را حفظ کنید، اما باید بدانید درخواست شبکه منتظر نمی‌ماند تا کل برنامه را متوقف کند.

Promise چیست؟

Promise نتیجه آینده یک عملیات غیرهم‌زمان را نمایش می‌دهد.

Promise سه حالت اصلی دارد:

  • Pending
  • Fulfilled
  • Rejected

مثال:

fetch("/api/status")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

Async و Await

async و await نوشتن کد Promise-based را خواناتر می‌کنند:

async function getStatus() {
  try {
    const response = await fetch("/api/status");

    if (!response.ok) {
      throw new Error(
        `HTTP error: ${response.status}`
      );
    }

    const data = await response.json();

    return data;
  } catch (error) {
    console.error("Request failed:", error);
    throw error;
  }
}

تابع async همیشه Promise برمی‌گرداند. توضیحات تکمیلی در مستندات async function در MDN موجود است.

مدیریت چند عملیات غیرهم‌زمان

اگر عملیات‌ها مستقل هستند، می‌توان آن‌ها را هم‌زمان آغاز کرد:

const [models, usage] = await Promise.all([
  fetch("/api/models").then((response) =>
    response.json()
  ),

  fetch("/api/usage").then((response) =>
    response.json()
  )
]);

Promise.all با ردشدن یکی از Promiseها رد می‌شود.

برای دریافت نتیجه همه عملیات، حتی در صورت خطای بعضی از آن‌ها:

const results = await Promise.allSettled([
  loadModels(),
  loadUsage(),
  loadProfile()
]);

Fetch API چیست؟

Fetch API برای ارسال درخواست HTTP در مرورگر و برخی Runtimeهای JavaScript استفاده می‌شود.

درخواست GET:

const response = await fetch("/api/models");

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const models = await response.json();

درخواست POST:

const response = await fetch("/api/chat", {
  method: "POST",

  headers: {
    "Content-Type": "application/json"
  },

  body: JSON.stringify({
    messages: [
      {
        role: "user",
        content: "سلام"
      }
    ]
  })
});

نکته مهم: fetch معمولاً برای پاسخ HTTP مانند ۴۰۰ یا ۵۰۰ به‌تنهایی Promise را Reject نمی‌کند. باید response.ok یا response.status را بررسی کنید.

راهنمای کامل این API در مستندات Fetch در MDN قرار دارد.

لغو درخواست با AbortController

کاربر ممکن است بخواهد درخواست طولانی را متوقف کند:

const controller = new AbortController();

const response = await fetch("/api/chat", {
  method: "POST",
  signal: controller.signal
});

// در صورت نیاز:
controller.abort();

برای Timeout:

async function fetchWithTimeout(
  url,
  options = {},
  timeoutMs = 60000
) {
  const controller = new AbortController();

  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeoutMs);

  try {
    return await fetch(url, {
      ...options,
      signal: controller.signal
    });
  } finally {
    clearTimeout(timeoutId);
  }
}

خطا در JavaScript

ساخت خطا

throw new Error("متن پیام نمی‌تواند خالی باشد");

try و catch

try {
  const response = await sendMessage();
  console.log(response);
} catch (error) {
  console.error(error.message);
}

finally

setLoading(true);

try {
  await sendMessage();
} catch (error) {
  showError(error.message);
} finally {
  setLoading(false);
}

finally چه درخواست موفق شود و چه خطا دهد اجرا خواهد شد.

Module در JavaScript

به‌جای نگهداری تمام کد در یک فایل، آن را به Moduleهای کوچک تقسیم کنید.

فایل validation.js:

export function validatePrompt(prompt) {
  const normalized = prompt.trim();

  if (!normalized) {
    throw new Error("پیام نمی‌تواند خالی باشد.");
  }

  if (normalized.length > 4000) {
    throw new Error(
      "طول پیام نباید بیشتر از ۴۰۰۰ کاراکتر باشد."
    );
  }

  return normalized;
}

فایل main.js:

import { validatePrompt } from "./validation.js";

const prompt = validatePrompt("  سلام  ");

console.log(prompt);

HTML:

<script type="module" src="./main.js"></script>

Local Storage

برای ذخیره تنظیمات کوچک در مرورگر:

localStorage.setItem("theme", "dark");

const theme =
  localStorage.getItem("theme") ?? "light";

ذخیره Object:

const settings = {
  theme: "dark",
  compactMode: true
};

localStorage.setItem(
  "settings",
  JSON.stringify(settings)
);

بازیابی:

const rawSettings =
  localStorage.getItem("settings");

const settings = rawSettings
  ? JSON.parse(rawSettings)
  : {};

کلید API، رمز عبور و Secret را در Local Storage نگهداری نکنید. داده‌های Local Storage برای JavaScript همان Origin قابل‌دسترسی هستند.

پروژه عملی: ساخت دستیار هوش مصنوعی با JavaScript

اکنون یک پروژه کامل می‌سازیم که شامل موارد زیر است:

  • رابط HTML
  • JavaScript سمت مرورگر
  • مدیریت DOM و Event
  • تاریخچه کوتاه مکالمه
  • Fetch API
  • Async/Await
  • مدیریت Loading و خطا
  • امکان توقف درخواست
  • Backend با Node.js و Express
  • اعتبارسنجی ورودی
  • نگهداری API Key در سرور
  • اتصال به API درواره

معماری پروژه:

مرورگر کاربر
    ↓
JavaScript Frontend
    ↓
Backend با Node.js و Express
    ↓
API درواره
    ↓
مدل هوش مصنوعی انتخاب‌شده

ساختار پروژه

javascript-ai-assistant/
├── public/
│   ├── index.html
│   ├── styles.css
│   └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js

مرحله اول: ایجاد پروژه

mkdir javascript-ai-assistant
cd javascript-ai-assistant
npm init -y

نصب وابستگی‌ها:

npm install express dotenv

ساخت پوشه Frontend:

mkdir public

مرحله دوم: تنظیم package.json

فایل package.json را به شکل زیر تنظیم کنید:

{
  "name": "javascript-ai-assistant",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch server.js"
  },
  "dependencies": {
    "dotenv": "^17.0.0",
    "express": "^5.0.0"
  }
}

نسخه دقیق وابستگی‌ها ممکن است هنگام نصب تغییر کرده باشد. پس از اجرای npm install، نسخه‌های واقعی در package.json و package-lock.json ثبت می‌شوند.

وجود "type": "module" امکان استفاده از import و export را در Node.js فراهم می‌کند.

مرحله سوم: ساخت رابط HTML

فایل public/index.html:

<!doctype html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="UTF-8">

  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >

  <meta
    name="description"
    content="دستیار هوش مصنوعی فارسی با JavaScript و API درواره"
  >

  <title>دستیار JavaScript درواره</title>

  <link rel="stylesheet" href="/styles.css">
</head>

<body>
  <main class="app">
    <header class="app__header">
      <div>
        <p class="eyebrow">پروژه آموزش JavaScript</p>
        <h1>دستیار هوش مصنوعی درواره</h1>
      </div>

      <span class="status" id="status">
        آماده
      </span>
    </header>

    <section
      class="messages"
      id="messages"
      aria-label="پیام‌های گفت‌وگو"
      aria-live="polite"
    >
      <article class="message message--assistant">
        <strong>دستیار درواره</strong>

        <p>
          سلام! پیام خود را بنویسید تا گفت‌وگو را شروع کنیم.
        </p>
      </article>
    </section>

    <form class="composer" id="chat-form">
      <label for="prompt">
        پیام شما
      </label>

      <textarea
        id="prompt"
        name="prompt"
        rows="3"
        maxlength="4000"
        placeholder="برای مثال: Promise در JavaScript چیست؟"
        required
      ></textarea>

      <div class="composer__actions">
        <button
          class="button button--secondary"
          id="stop-button"
          type="button"
          disabled
        >
          توقف
        </button>

        <button
          class="button button--primary"
          id="send-button"
          type="submit"
        >
          ارسال پیام
        </button>
      </div>
    </form>

    <p class="notice">
      پاسخ هوش مصنوعی ممکن است نیازمند بررسی باشد.
    </p>
  </main>

  <script
    type="module"
    src="/app.js"
  ></script>
</body>
</html>

مرحله چهارم: استایل رابط

فایل public/styles.css:

:root {
  --primary: #6d4aff;
  --primary-dark: #5637df;
  --background: #f5f6fb;
  --surface: #ffffff;
  --text: #182033;
  --muted: #697386;
  --border: #e2e6ef;
  --error: #b42318;
  --success: #087443;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  min-width: 320px;
  min-height: 100dvh;
  margin: 0;
  padding: 24px;

  color: var(--text);
  background:
    radial-gradient(
      circle at top right,
      rgb(109 74 255 / 12%),
      transparent 30%
    ),
    var(--background);

  font-family:
    Vazirmatn,
    Tahoma,
    Arial,
    sans-serif;

  line-height: 1.8;
}

button,
textarea {
  font: inherit;
}

.app {
  display: grid;
  grid-template-rows: auto minmax(320px, 1fr) auto auto;
  gap: 20px;

  width: min(100%, 900px);
  min-height: calc(100dvh - 48px);
  margin-inline: auto;
  padding: clamp(18px, 4vw, 36px);

  background-color: rgb(255 255 255 / 92%);
  border: 1px solid var(--border);
  border-radius: 24px;
  box-shadow:
    0 24px 70px rgb(20 24 40 / 9%);
}

.app__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.app__header h1 {
  margin: 0;
  font-size: clamp(1.3rem, 4vw, 2rem);
}

.eyebrow {
  margin: 0 0 4px;
  color: var(--primary);
  font-size: 0.8rem;
  font-weight: 700;
}

.status {
  padding: 6px 12px;

  color: var(--success);
  background-color: #ecfdf3;

  border: 1px solid #abefc6;
  border-radius: 999px;
  font-size: 0.78rem;
}

.status--loading {
  color: #6941c6;
  background-color: #f4f0ff;
  border-color: #d9d0ff;
}

.status--error {
  color: var(--error);
  background-color: #fef3f2;
  border-color: #fecdca;
}

.messages {
  min-height: 0;
  padding: 16px;

  background-color: #f8f9fc;
  border: 1px solid var(--border);
  border-radius: 18px;

  overflow-y: auto;
  overscroll-behavior: contain;
}

.message {
  width: fit-content;
  max-width: min(82%, 680px);
  margin-block-end: 16px;
  padding: 13px 16px;

  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;

  overflow-wrap: anywhere;
}

.message strong {
  display: block;
  margin-block-end: 4px;
  font-size: 0.75rem;
}

.message p {
  margin: 0;
  white-space: pre-wrap;
}

.message--user {
  margin-inline-start: auto;

  color: #ffffff;
  background-color: var(--primary);
  border-color: var(--primary);
}

.message--error {
  color: var(--error);
  background-color: #fef3f2;
  border-color: #fecdca;
}

.composer {
  display: grid;
  gap: 10px;
}

.composer label {
  font-size: 0.85rem;
  font-weight: 700;
}

.composer textarea {
  width: 100%;
  min-height: 110px;
  max-height: 220px;
  padding: 14px 16px;

  color: var(--text);
  background-color: var(--surface);

  border: 1px solid var(--border);
  border-radius: 14px;

  line-height: 1.8;
  resize: vertical;
}

.composer textarea:focus {
  border-color: var(--primary);
  outline: 4px solid rgb(109 74 255 / 10%);
}

.composer__actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.button {
  min-height: 44px;
  padding: 9px 18px;

  border: 0;
  border-radius: 12px;

  cursor: pointer;
}

.button--primary {
  color: #ffffff;
  background-color: var(--primary);
}

.button--primary:hover:not(:disabled) {
  background-color: var(--primary-dark);
}

.button--secondary {
  color: var(--text);
  background-color: #eef0f5;
}

.button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.button:focus-visible {
  outline: 3px solid rgb(109 74 255 / 30%);
  outline-offset: 3px;
}

.notice {
  margin: 0;
  color: var(--muted);
  font-size: 0.75rem;
  text-align: center;
}

@media (max-width: 600px) {
  body {
    padding: 0;
  }

  .app {
    min-height: 100dvh;
    padding: 18px;
    border: 0;
    border-radius: 0;
  }

  .app__header {
    align-items: flex-start;
  }

  .message {
    max-width: 90%;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

مرحله پنجم: برنامه Frontend

فایل public/app.js:

const form = document.querySelector("#chat-form");
const input = document.querySelector("#prompt");
const messagesElement =
  document.querySelector("#messages");

const sendButton =
  document.querySelector("#send-button");

const stopButton =
  document.querySelector("#stop-button");

const statusElement =
  document.querySelector("#status");

const conversation = [];

let activeController = null;
let isLoading = false;

function setStatus(text, type = "ready") {
  statusElement.textContent = text;

  statusElement.classList.toggle(
    "status--loading",
    type === "loading"
  );

  statusElement.classList.toggle(
    "status--error",
    type === "error"
  );
}

function setLoading(value) {
  isLoading = value;

  sendButton.disabled = value;
  input.disabled = value;
  stopButton.disabled = !value;

  if (value) {
    setStatus("در حال دریافت پاسخ", "loading");
  } else {
    setStatus("آماده");
    input.disabled = false;
    input.focus();
  }
}

function scrollToLatestMessage() {
  messagesElement.scrollTop =
    messagesElement.scrollHeight;
}

function createMessageElement(
  role,
  content,
  options = {}
) {
  const article =
    document.createElement("article");

  article.className = `message message--${role}`;

  if (options.isError) {
    article.classList.add("message--error");
  }

  const title =
    document.createElement("strong");

  title.textContent =
    role === "user"
      ? "شما"
      : "دستیار درواره";

  const paragraph =
    document.createElement("p");

  paragraph.textContent = content;

  article.append(title, paragraph);

  return article;
}

function appendMessage(role, content, options) {
  const message = createMessageElement(
    role,
    content,
    options
  );

  messagesElement.append(message);
  scrollToLatestMessage();

  return message;
}

function validatePrompt(value) {
  const prompt = value.trim();

  if (!prompt) {
    throw new Error("پیام نمی‌تواند خالی باشد.");
  }

  if (prompt.length > 4000) {
    throw new Error(
      "پیام نباید بیشتر از ۴۰۰۰ کاراکتر باشد."
    );
  }

  return prompt;
}

async function parseResponse(response) {
  const contentType =
    response.headers.get("content-type") || "";

  if (!contentType.includes("application/json")) {
    throw new Error(
      "پاسخ سرور در قالب مورد انتظار نیست."
    );
  }

  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      data.error ||
      `درخواست ناموفق بود. کد: ${response.status}`
    );
  }

  if (
    typeof data.answer !== "string" ||
    !data.answer.trim()
  ) {
    throw new Error(
      "متن معتبری در پاسخ سرور وجود ندارد."
    );
  }

  return data.answer;
}

async function requestAssistant(messages) {
  activeController = new AbortController();

  const response = await fetch("/api/chat", {
    method: "POST",

    headers: {
      "Content-Type": "application/json"
    },

    body: JSON.stringify({
      messages
    }),

    signal: activeController.signal
  });

  return parseResponse(response);
}

async function handleSubmit(event) {
  event.preventDefault();

  if (isLoading) {
    return;
  }

  let prompt;

  try {
    prompt = validatePrompt(input.value);
  } catch (error) {
    setStatus(error.message, "error");
    input.focus();
    return;
  }

  const userMessage = {
    role: "user",
    content: prompt
  };

  conversation.push(userMessage);
  appendMessage("user", prompt);

  input.value = "";
  setLoading(true);

  const loadingMessage = appendMessage(
    "assistant",
    "در حال آماده‌سازی پاسخ..."
  );

  try {
    const answer = await requestAssistant(
      conversation
    );

    loadingMessage.remove();

    conversation.push({
      role: "assistant",
      content: answer
    });

    appendMessage("assistant", answer);
  } catch (error) {
    loadingMessage.remove();

    if (error.name === "AbortError") {
      appendMessage(
        "assistant",
        "درخواست توسط کاربر متوقف شد."
      );

      return;
    }

    appendMessage(
      "assistant",
      `خطا: ${error.message}`,
      {
        isError: true
      }
    );

    setStatus("خطا در دریافت پاسخ", "error");
  } finally {
    activeController = null;
    setLoading(false);
  }
}

form.addEventListener("submit", handleSubmit);

stopButton.addEventListener("click", () => {
  activeController?.abort();
});

input.addEventListener("keydown", (event) => {
  if (
    event.key === "Enter" &&
    !event.shiftKey &&
    !event.isComposing
  ) {
    event.preventDefault();
    form.requestSubmit();
  }
});

این فایل چند مفهوم اصلی JavaScript را در یک پروژه واقعی ترکیب می‌کند:

  • انتخاب عناصر DOM
  • تعریف State
  • تابع
  • Object و Array
  • Event Listener
  • اعتبارسنجی ورودی
  • Async/Await
  • Fetch API
  • AbortController
  • Try/Catch/Finally
  • بررسی پاسخ HTTP
  • ساخت عناصر DOM
  • استفاده از textContent
  • Optional Chaining

مرحله ششم: ساخت Backend با Node.js

فایل server.js:

import "dotenv/config";

import express from "express";

const app = express();

const port = Number(
  process.env.PORT || 3000
);

const apiKey =
  process.env.DARVAREH_API_KEY;

const modelId =
  process.env.DARVAREH_MODEL_ID;

const darvarehChatUrl =
  "https://api.darvareh.ir/v1/chat/completions";

app.disable("x-powered-by");

app.use(
  express.json({
    limit: "64kb"
  })
);

app.use(
  express.static("public")
);

function isValidMessage(message) {
  return (
    message !== null &&
    typeof message === "object" &&
    ["user", "assistant"].includes(message.role) &&
    typeof message.content === "string" &&
    message.content.trim().length > 0 &&
    message.content.length <= 10000
  );
}

function validateMessages(messages) {
  if (!Array.isArray(messages)) {
    return false;
  }

  if (
    messages.length === 0 ||
    messages.length > 30
  ) {
    return false;
  }

  return messages.every(isValidMessage);
}

function extractAnswer(data) {
  const answer =
    data?.choices?.[0]?.message?.content;

  if (
    typeof answer !== "string" ||
    !answer.trim()
  ) {
    throw new Error(
      "ساختار پاسخ مدل معتبر نیست."
    );
  }

  return answer;
}

app.get("/api/status", (request, response) => {
  response.json({
    status: "ok"
  });
});

app.post("/api/chat", async (request, response) => {
  if (!apiKey || !modelId) {
    return response.status(500).json({
      error:
        "تنظیمات API در سرور کامل نیست."
    });
  }

  const { messages } = request.body ?? {};

  if (!validateMessages(messages)) {
    return response.status(400).json({
      error:
        "ساختار یا تعداد پیام‌ها معتبر نیست."
    });
  }

  const controller = new AbortController();

  const timeoutId = setTimeout(() => {
    controller.abort();
  }, 60000);

  try {
    const upstreamResponse = await fetch(
      darvarehChatUrl,
      {
        method: "POST",

        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json"
        },

        body: JSON.stringify({
          model: modelId,

          messages: [
            {
              role: "system",
              content:
                "شما یک دستیار فارسی، دقیق و مفید هستید. " +
                "پاسخ‌ها را واضح و ساختاریافته ارائه کنید."
            },

            ...messages
          ],

          temperature: 0.4,
          max_tokens: 1200
        }),

        signal: controller.signal
      }
    );

    const rawBody =
      await upstreamResponse.text();

    let data;

    try {
      data = JSON.parse(rawBody);
    } catch {
      return response.status(502).json({
        error:
          "پاسخ سرویس مدل قابل پردازش نبود."
      });
    }

    if (!upstreamResponse.ok) {
      console.error(
        "Darvareh API error:",
        upstreamResponse.status
      );

      return response.status(502).json({
        error:
          "سرویس مدل پاسخ موفقی ارسال نکرد."
      });
    }

    const answer = extractAnswer(data);

    return response.json({
      answer
    });
  } catch (error) {
    if (error.name === "AbortError") {
      return response.status(504).json({
        error:
          "زمان انتظار برای پاسخ به پایان رسید."
      });
    }

    console.error(
      "Chat request failed:",
      error.message
    );

    return response.status(502).json({
      error:
        "ارتباط با سرویس مدل برقرار نشد."
    });
  } finally {
    clearTimeout(timeoutId);
  }
});

app.use((request, response) => {
  response.status(404).json({
    error: "مسیر درخواستی پیدا نشد."
  });
});

app.listen(port, () => {
  console.log(
    `Server is running on http://localhost:${port}`
  );
});

مرحله هفتم: تنظیم متغیرهای محیطی

فایل .env:

PORT=3000
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

برای دریافت کلید API می‌توانید در درواره ثبت‌نام کنید.

برای انتخاب مدل، مشاهده Model ID و بررسی اطلاعات قیمت به صفحه مدل‌های درواره مراجعه کنید.

کلید واقعی API را در آموزش، Repository عمومی، JavaScript مرورگر یا پیام‌های پشتیبانی منتشر نکنید.

مرحله هشتم: ساخت gitignore

فایل .gitignore:

node_modules/
.env
npm-debug.log*
.DS_Store

فایل package-lock.json را معمولاً وارد Git کنید تا نسخه وابستگی‌ها در نصب‌های مختلف قابل‌پیش‌بینی‌تر باشد.

مرحله نهم: اجرای پروژه

حالت عادی:

npm start

حالت توسعه:

npm run dev

سپس آدرس زیر را در مرورگر باز کنید:

http://localhost:3000

مسیر کامل یک پیام در پروژه

وقتی کاربر پیام را ارسال می‌کند:

  1. رویداد submit فرم اجرا می‌شود.
  2. رفتار پیش‌فرض فرم متوقف می‌شود.
  3. متن با trim() پاک‌سازی اولیه می‌شود.
  4. طول و خالی‌نبودن پیام بررسی می‌شود.
  5. پیام در DOM نمایش داده می‌شود.
  6. پیام به Array تاریخچه افزوده می‌شود.
  7. حالت Loading فعال می‌شود.
  8. Frontend به /api/chat درخواست POST می‌فرستد.
  9. Backend ساختار پیام‌ها را اعتبارسنجی می‌کند.
  10. Backend کلید API را از متغیر محیطی می‌خواند.
  11. درخواست به API درواره ارسال می‌شود.
  12. پاسخ مدل در Backend بررسی می‌شود.
  13. فقط متن موردنیاز به Frontend بازگردانده می‌شود.
  14. Frontend پاسخ را با textContent نمایش می‌دهد.
  15. حالت Loading غیرفعال می‌شود.

چرا مستقیماً از مرورگر به API درواره درخواست نمی‌فرستیم؟

برای ارسال مستقیم باید API Key داخل کد مرورگر قرار گیرد:

// این کار را انجام ندهید
const apiKey = "YOUR_DARVAREH_API_KEY";

کد HTML و JavaScript مرورگر برای کاربر قابل‌مشاهده است. Minify یا Obfuscate کردن کد نیز کلید را به Secret واقعی تبدیل نمی‌کند.

استفاده از Backend مزایای دیگری نیز دارد:

  • محدودکردن طول ورودی
  • کنترل تعداد پیام‌ها
  • Rate Limiting
  • ثبت میزان مصرف
  • کنترل دسترسی کاربران
  • انتخاب Model ID
  • مدیریت Timeout
  • نگهداری Secret
  • یکسان‌سازی پیام‌های خطا
  • جلوگیری از نمایش جزئیات سرویس داخلی

محدودکردن تاریخچه گفت‌وگو

اگر تمام پیام‌های یک مکالمه طولانی در هر درخواست ارسال شوند، مصرف Token و هزینه افزایش پیدا می‌کند.

یک راه ساده در Frontend:

function getRecentMessages(messages, limit = 12) {
  return messages.slice(-limit);
}

هنگام درخواست:

const answer = await requestAssistant(
  getRecentMessages(conversation)
);

در سیستم حرفه‌ای‌تر می‌توان:

  • پیام‌های قدیمی را خلاصه کرد.
  • تعداد Tokenها را تخمین زد.
  • System Prompt را جداگانه مدیریت کرد.
  • مکالمه را در دیتابیس نگهداری کرد.
  • Context مرتبط را انتخاب کرد.
  • محدودیت هر مدل را در نظر گرفت.

جلوگیری از درخواست هم‌زمان تکراری

در پروژه، متغیر isLoading مانع ارسال چندباره فرم می‌شود:

if (isLoading) {
  return;
}

دکمه نیز غیرفعال می‌شود:

sendButton.disabled = true;

بااین‌حال، کنترل Frontend کافی نیست. در پروژه Production باید محدودیت‌های لازم در Backend نیز اعمال شوند؛ زیرا کاربر می‌تواند خارج از رابط مستقیماً به Endpoint درخواست بفرستد.

مدیریت خطای بهتر

پیام خطا برای کاربر باید قابل‌فهم باشد، اما جزئیات حساس داخلی را نمایش ندهد.

نمونه مناسب برای کاربر:

{
  "error": "سرویس مدل موقتاً در دسترس نیست."
}

Log سرور می‌تواند جزئیات فنی بیشتری داشته باشد:

console.error({
  event: "upstream_request_failed",
  status: upstreamResponse.status
});

API Key، Authorization Header و متن حساس کاربر را بدون ضرورت در Log ذخیره نکنید.

اضافه‌کردن Retry کنترل‌شده

Retry باید فقط برای خطاهای موقت و با تعداد محدود انجام شود. درخواست نامعتبر یا خطای احراز هویت معمولاً با تکرار حل نمی‌شود.

function wait(milliseconds) {
  return new Promise((resolve) => {
    setTimeout(resolve, milliseconds);
  });
}

async function fetchWithRetry(
  url,
  options,
  maxAttempts = 3
) {
  let lastError;

  for (
    let attempt = 1;
    attempt <= maxAttempts;
    attempt += 1
  ) {
    try {
      const response = await fetch(url, options);

      if (
        response.ok ||
        ![429, 502, 503, 504].includes(
          response.status
        )
      ) {
        return response;
      }

      lastError = new Error(
        `Temporary HTTP error: ${response.status}`
      );
    } catch (error) {
      lastError = error;
    }

    if (attempt < maxAttempts) {
      const delay =
        500 * 2 ** (attempt - 1);

      await wait(delay);
    }
  }

  throw lastError;
}

در Production باید موارد زیر نیز بررسی شوند:

  • Retry-After
  • Idempotency
  • Timeout کلی
  • Jitter
  • حداکثر تعداد Retry
  • بودجه درخواست
  • لغو درخواست کاربر

Streaming پاسخ چیست؟

در حالت عادی، Frontend تا کامل‌شدن پاسخ صبر می‌کند. در Streaming، بخش‌های پاسخ به‌تدریج نمایش داده می‌شوند.

معماری مفهومی:

مدل
  ↓ بخش‌های پاسخ
Backend
  ↓ Stream
مرورگر
  ↓ نمایش تدریجی
رابط کاربری

Streaming تجربه کاربری را بهتر می‌کند، اما پیاده‌سازی آن به مدیریت موارد زیر نیاز دارد:

  • خواندن Stream
  • Decode کردن Chunkها
  • تشخیص پایان پاسخ
  • مدیریت قطع اتصال
  • لغو درخواست
  • ذخیره پاسخ نهایی
  • مدیریت خطای میانه جریان

برای اولین پروژه بهتر است ابتدا نسخه غیرStreaming را کامل و پایدار کنید و سپس Streaming را اضافه کنید.

تست واحد برای تابع اعتبارسنجی

Node.js دارای ابزار تست داخلی است. منطق اعتبارسنجی را در فایل جداگانه قرار دهید.

فایل validation.js:

export function validatePrompt(value) {
  if (typeof value !== "string") {
    throw new TypeError(
      "Prompt must be a string"
    );
  }

  const prompt = value.trim();

  if (!prompt) {
    throw new Error(
      "Prompt cannot be empty"
    );
  }

  if (prompt.length > 4000) {
    throw new Error(
      "Prompt is too long"
    );
  }

  return prompt;
}

فایل validation.test.js:

import test from "node:test";
import assert from "node:assert/strict";

import {
  validatePrompt
} from "./validation.js";

test("trims a valid prompt", () => {
  assert.equal(
    validatePrompt("  سلام  "),
    "سلام"
  );
});

test("rejects an empty prompt", () => {
  assert.throws(
    () => validatePrompt("   "),
    /cannot be empty/
  );
});

test("rejects non-string input", () => {
  assert.throws(
    () => validatePrompt(null),
    TypeError
  );
});

اجرا:

node --test

اشتباه‌های رایج JavaScript

فراموش‌کردن await

const response = fetch("/api/chat");

console.log(response);

در اینجا response یک Promise است.

نسخه صحیح داخل تابع Async:

const response = await fetch("/api/chat");

بررسی‌نکردن response.ok

const response = await fetch("/api/chat");
const data = await response.json();

نسخه بهتر:

const response = await fetch("/api/chat");

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const data = await response.json();

استفاده از forEach با await

این الگو منتظر پایان همه عملیات نمی‌ماند:

items.forEach(async (item) => {
  await processItem(item);
});

اجرای ترتیبی:

for (const item of items) {
  await processItem(item);
}

اجرای هم‌زمان:

await Promise.all(
  items.map((item) => processItem(item))
);

مقایسه Objectها

console.log({} === {}); // false

هر Object Reference متفاوتی دارد:

const object = {};

console.log(object === object); // true

تغییر ناخواسته Object

const settings = {
  temperature: 0.4
};

const copy = settings;

copy.temperature = 0.8;

console.log(settings.temperature); // 0.8

کپی سطحی:

const copy = {
  ...settings
};

استفاده اشتباه از innerHTML

output.innerHTML = userInput;

برای متن کنترل‌نشده:

output.textContent = userInput;

قراردادن API Key در Frontend

کلید API را در این مکان‌ها قرار ندهید:

  • فایل JavaScript مرورگر
  • HTML
  • CSS
  • Local Storage
  • Repository عمومی
  • URL
  • Query String
  • کد نمونه منتشرشده

Debug کردن JavaScript

console.log

console.log("messages:", conversation);

برای خطا:

console.error("request failed:", error);

برای نمایش جدولی:

console.table(conversation);

قبل از انتشار، Logهای اضافی یا حاوی اطلاعات حساس را حذف کنید.

Breakpoint

در DevTools مرورگر:

  1. بخش Sources را باز کنید.
  2. فایل JavaScript را انتخاب کنید.
  3. روی شماره خط کلیک کنید.
  4. عملیات را در صفحه تکرار کنید.
  5. مقدار متغیرها و Call Stack را بررسی کنید.

همچنین می‌توان موقتاً از دستور زیر استفاده کرد:

debugger;

Network Panel

برای بررسی درخواست API:

  • URL صحیح است؟
  • Method درست است؟
  • Request Body ارسال شده؟
  • Content-Type چیست؟
  • Status Code چند است؟
  • پاسخ JSON معتبر است؟
  • مدت درخواست چقدر بوده؟
  • آیا درخواست لغو شده است؟

Authorization درخواست Backend به درواره در Network Panel مرورگر دیده نمی‌شود؛ زیرا آن درخواست از سرور انجام می‌شود.

JavaScript یا TypeScript؟

معیارJavaScriptTypeScript
اجرای مستقیمبلهمعمولاً پس از تبدیل
یادگیری اولیهساده‌ترنیازمند شناخت Type
بررسی نوعدر Runtimeعمدتاً هنگام توسعه
پروژه کوچکمناسببسته به نیاز
پروژه بزرگنیازمند انضباط بیشترمعمولاً قابل‌نگهداری‌تر
کتابخانه وبپایه اکوسیستمتبدیل‌شونده به JavaScript

مسیر پیشنهادی:

  1. JavaScript پایه را یاد بگیرید.
  2. DOM، Fetch و Async/Await را تمرین کنید.
  3. Module و مدیریت خطا را یاد بگیرید.
  4. سپس سراغ TypeScript بروید.

TypeScript جای JavaScript را حذف نمی‌کند؛ در نهایت کد آن برای اجرا به JavaScript تبدیل می‌شود.

JavaScript یا Python برای هوش مصنوعی؟

Python برای علم داده، Machine Learning و آموزش مدل‌ها اکوسیستم بسیار بزرگی دارد. JavaScript برای ساخت محصول وب، رابط کاربری و سرویس‌های Full Stack گزینه مهمی است.

کاربردانتخاب رایج
آموزش مدل Machine LearningPython
تحلیل دادهPython
رابط وبJavaScript یا TypeScript
Backend وبهر دو
اپلیکیشن Full StackJavaScript یا TypeScript
اتصال ساده به API مدلهر دو
ابزار مرورگرJavaScript
افزونه ChromeJavaScript یا TypeScript

اگر هدف شما ساخت محصول وب مبتنی بر API هوش مصنوعی است، JavaScript می‌تواند Frontend و Backend را با یک زبان پوشش دهد.

استفاده حرفه‌ای از JavaScript در پروژه هوش مصنوعی

در پروژه Production بهتر است این اجزا را از یکدیگر جدا کنید:

src/
├── config/
│   └── env.js
├── routes/
│   └── chat.js
├── services/
│   └── darvareh.js
├── validators/
│   └── chat.js
├── middleware/
│   └── error-handler.js
├── public/
│   ├── index.html
│   ├── styles.css
│   └── app.js
└── server.js

مسئولیت هر بخش:

  • config: تنظیمات و متغیرهای محیطی
  • routes: Endpointهای HTTP
  • services: ارتباط با سرویس خارجی
  • validators: بررسی ورودی
  • middleware: مدیریت خطا و درخواست
  • public: رابط مرورگر
  • server.js: راه‌اندازی برنامه

این ساختار برای پروژه بسیار کوچک ضروری نیست، اما با رشد محصول از بزرگ‌شدن یک فایل جلوگیری می‌کند.

چک‌لیست پروژه JavaScript

پیش از انتشار بررسی کنید:

  • از const و let به‌درستی استفاده شده است.
  • مقایسه‌ها با === و !== انجام می‌شوند.
  • ورودی کاربر در Frontend و Backend اعتبارسنجی می‌شود.
  • پاسخ HTTP با response.ok بررسی می‌شود.
  • خطاها با try/catch مدیریت می‌شوند.
  • حالت Loading و Disabled وجود دارد.
  • کاربر می‌تواند درخواست طولانی را لغو کند.
  • Timeout در Backend تعریف شده است.
  • API Key فقط در Backend نگهداری می‌شود.
  • فایل .env وارد Git نمی‌شود.
  • پاسخ مدل با textContent نمایش داده می‌شود.
  • اطلاعات حساس در Log ثبت نمی‌شوند.
  • تعداد پیام‌های تاریخچه محدود است.
  • خطای سرویس خارجی مستقیماً به کاربر نمایش داده نمی‌شود.
  • کد به Moduleهای قابل‌فهم تقسیم شده است.
  • توابع اصلی تست دارند.
  • وابستگی‌ها به‌روز و موردنیاز هستند.
  • Model ID از تنظیمات خوانده می‌شود.
  • قیمت‌ها به‌صورت ثابت و غیرقابل‌به‌روزرسانی در کد قرار نگرفته‌اند.

نقشه راه یادگیری JavaScript

مرحله اول: مبانی

  • متغیر
  • انواع داده
  • Operator
  • شرط
  • حلقه
  • تابع

مرحله دوم: ساختار داده

  • Array
  • Object
  • Map
  • Set
  • JSON
  • Destructuring
  • Spread

مرحله سوم: مرورگر

  • DOM
  • Event
  • Form
  • Class
  • Attribute
  • Local Storage

مرحله چهارم: برنامه‌نویسی غیرهم‌زمان

  • Event Loop
  • Promise
  • Async/Await
  • Fetch API
  • AbortController
  • Error Handling

مرحله پنجم: معماری

  • Module
  • Scope
  • Closure
  • Class
  • جداسازی مسئولیت‌ها
  • اعتبارسنجی
  • تست

مرحله ششم: توسعه محصول

  • Node.js
  • Express
  • REST API
  • دیتابیس
  • احراز هویت
  • Logging
  • Deployment
  • TypeScript

سؤال‌های متداول

JavaScript چیست؟

JavaScript زبان برنامه‌نویسی اصلی ایجاد تعامل در وب است. این زبان در مرورگر و محیط‌هایی مانند Node.js اجرا می‌شود و برای Frontend، Backend، افزونه مرورگر و اپلیکیشن‌های مختلف کاربرد دارد.

آیا JavaScript همان Java است؟

خیر. این دو زبان از نظر نوع‌دهی، Runtime، Syntax و کاربرد تفاوت‌های اساسی دارند.

آیا یادگیری JavaScript سخت است؟

شروع آن نسبتاً ساده است، اما تسلط بر Scope، Closure، Async Programming، Event Loop، Module و معماری پروژه به تمرین نیاز دارد.

برای یادگیری React ابتدا باید JavaScript بلد باشیم؟

بله. قبل از React بهتر است تابع، Array، Object، Destructuring، Module، Promise، Async/Await و DOM را یاد بگیرید.

JavaScript در Backend هم استفاده می‌شود؟

بله. با Node.js می‌توان Web Server، REST API، Worker، ابزار خط فرمان و Backend اپلیکیشن‌های هوش مصنوعی را ساخت.

تفاوت let و const چیست؟

متغیر const دوباره مقداردهی نمی‌شود، اما let می‌تواند مقدار جدید دریافت کند. بهتر است پیش‌فرض شما const باشد و فقط در صورت نیاز از let استفاده کنید.

تفاوت null و undefined چیست؟

undefined معمولاً یعنی مقداری تعریف یا اختصاص داده نشده است. null معمولاً نبودن آگاهانه مقدار را نشان می‌دهد.

Promise چیست؟

Promise نماینده نتیجه آینده یک عملیات غیرهم‌زمان است و می‌تواند Pending، Fulfilled یا Rejected باشد.

Async/Await چیست؟

async و await Syntax خواناتری برای کار با Promiseها فراهم می‌کنند. تابع Async همیشه Promise برمی‌گرداند.

Fetch API چیست؟

Fetch API رابطی برای ارسال درخواست HTTP است. با آن می‌توان اطلاعات را از Backend دریافت یا داده JSON ارسال کرد.

چرا fetch برای خطای ۴۰۴ وارد catch نمی‌شود؟

Fetch معمولاً خطاهای HTTP را به‌تنهایی Reject نمی‌کند. باید response.ok یا response.status را بررسی کنید. خطاهای شبکه یا لغو درخواست معمولاً باعث Reject شدن Promise می‌شوند.

آیا می‌توان API هوش مصنوعی را مستقیماً از JavaScript مرورگر فراخوانی کرد؟

از نظر فنی ممکن است، اما اگر API به Secret Key نیاز دارد نباید کلید را در مرورگر قرار دهید. درخواست باید از Backend شما عبور کند.

آیا باید JavaScript یا TypeScript یاد بگیریم؟

ابتدا JavaScript را یاد بگیرید. سپس برای پروژه‌های بزرگ‌تر و بررسی نوع بهتر به TypeScript بروید.

JavaScript برای ساخت محصول هوش مصنوعی مناسب است؟

بله. JavaScript برای ساخت رابط وب، Backend، داشبورد، افزونه مرورگر و اپلیکیشن Full Stack متصل به API مدل‌ها مناسب است.

آیا JavaScript برای آموزش مدل هوش مصنوعی مناسب است؟

برای آموزش مدل و محاسبات علمی، Python معمولاً اکوسیستم گسترده‌تری دارد. JavaScript بیشتر برای ساخت محصول، رابط و اتصال به مدل‌های آماده استفاده می‌شود.

جمع‌بندی

JavaScript فقط زبانی برای نمایش Alert یا تغییر رنگ دکمه نیست. این زبان یکی از پایه‌های اصلی توسعه نرم‌افزار مدرن و ابزار مهم ساخت محصولات مبتنی بر هوش مصنوعی است.

برای کار حرفه‌ای با JavaScript باید این مفاهیم را یاد بگیرید:

  • متغیرها و انواع داده
  • شرط و حلقه
  • تابع و Scope
  • Array و Object
  • DOM و Event
  • JSON
  • Promise
  • Async/Await
  • Fetch API
  • مدیریت خطا
  • Module
  • Node.js و Backend

در پروژه عملی این مقاله، یک دستیار هوش مصنوعی واقعی ساختیم. رابط برنامه در مرورگر با JavaScript اجرا شد، Backend با Node.js و Express ساخته شد و درخواست مدل از طریق API درواره ارسال شد.

مهم‌ترین اصل معماری پروژه این بود که API Key در Frontend قرار نگیرد. مرورگر فقط با Backend خود برنامه ارتباط برقرار می‌کند و Backend مسئول نگهداری کلید، اعتبارسنجی ورودی، Timeout و اتصال به درواره است.

برای شروع، دریافت کلید API و اتصال پروژه JavaScript خود می‌توانید در درواره ثبت‌نام کنید. برای مشاهده مدل‌ها، انتخاب Model ID و بررسی اطلاعات به‌روز قیمت نیز به صفحه مدل‌های درواره مراجعه کنید.

منابع تکمیلی

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.