آموزش Node.js: آشنایی با استریم ها و کار با MySQL — بخش دوازدهم

۸۸ بازدید
آخرین به‌روزرسانی: ۰۱ مهر ۱۴۰۲
زمان مطالعه: ۷ دقیقه
آموزش Node.js: آشنایی با استریم ها و کار با MySQL — بخش دوازدهم

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

استریم‌ها مفهومی منحصر به Node.js نیستند. در واقع چند دهه قبل آن‌ها در سیستم عامل Unix معرفی شده‌اند و برنامه‌ها می‌توانند با ارسال استریم از طریق عملگر pipe (|) با هم ارتباط بگیرند. برای نمونه در روش سنتی زمانی که به برنامه‌ای اعلام می‌کنید که فایلی را بخواند، آن فایل از ابتدا تا انتها در حافظه خوانده می‌شود و سپس مورد پردازش قرار می‌گیرد.

با استفاده از استریم‌ها می‌توان فایل را قطعه به قطعه خواند و محتوی آن را بدون نیاز به نگهداری همه فایل در حافظه مورد پردازش قرار داد.

ماژول stream در Node.js بنیانی ارائه می‌کند که با استفاده از آن همه API-های استریمینگ ساخته می‌شوند.

چرا باید از استریم استفاده کرد؟

استریم‌ها اساساً دو مزیت عمده نسبت به دیگر روش‌های دستکاری داده ارائه می‌کنند:

  • کارایی حافظه: لازم نیست مقادیر بالایی از داده‌ها را در حافظه بارگذاری کنید تا بتوانید آن را پردازش کنید.
  • کارایی زمان: آغاز پردازش داده‌ها همزمان با خواندن اولین بخش بسیار سریع‌تر از این است که صبر کنیم همه داده‌ها بارگذاری شوند.

نمونه‌ای از یک استریم

یک نمونه معمول از استریم در زمان خواندن فایل از دیسک مشاهده می‌شود. با استفاده از ماژول fs در Node.js می‌توان یک فایل را خواند و زمانی که اتصال جدید با سرور http برقرار می‌شود، آن را روی HTTP عرضه کرد:

1const http = require('http') const fs = require('fs')
2const server = http.createServer(function(req, res) {
3    fs.readFile(__dirname + '/data.txt', (err, data) => {
4        res.end(data)
5    })
6}) server.listen(3000)

()readFile محتوای کامل فایل را می‌خواند و در زمان پایان یافتن، تابع callback را فرا می‌خواند. (res.end(data در callback محتوای فایل را به کلاینت http بازگشت می‌دهد.

اگر فایل بزرگ باشد، عملیات زمان نسبتاً زیادی می‌گیرد. در ادامه همین کد را می‌بینید که با استفاده از استریم‌ها نوشته شده است:

1const http = require('http') const fs = require('fs')
2
3const server = http.createServer((req, res) => {
4    const stream = fs.createReadStream(__dirname + '/data.txt') stream.pipe(res)
5}) server.listen(3000)

چنان که می‌بینید به جای انتظار برای خوانده شدن همه فایل، می‌توانیم به محض این که نخستین بخش داده‌ها آماده ارسال شدند، آن را به کلاینت http ارسال کنیم.

()pipe

در مثال بخش قبل از خطی به صورت (stream.pipe(res استفاده کردیم که در آن متد ()pipe روی استریم فایل فراخوانی می‌شود.

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

مقدار بازگشتی متد ()pipe استریم مقصد است که شیء بسیار آسانی برای زنجیره‌سازی چندین فراخوانی ()pipe برای ما فراهم می‌سازد. به مثال زیر توجه کنید:

1src.pipe(dest1).pipe(dest2)

این همانند آن است که کار زیر را انجام دهیم:

1src.pipe(dest1)dest1.pipe(dest2)

API-های Node.js مبتنی بر استریم

به دلیل مزیت استریم‌ها اغلب ماژول‌های هسته مرکزی Node.js مانند موارد زیر ظرفیت‌های مدیریت استریم نیتیو را ارائه می‌کنند:

  • process.stdin – یک استریم متصل به stdin بازگشت می‌دهد.
  • process.stdout – یک استریم متصل به stdout بازگشت می‌دهد.
  • process.stderr – یک استریم متصل به stderr بازگشت می‌دهد.
  • ()fs.createReadStream – یک استریم خواندنی در یک فایل ارائه می‌کند.
  • ()fs.createWriteStream – یک استریم نوشتنی در یک فایل ارائه می‌کند.
  • ()net.connect – یک اتصال مبتنی بر استریم باز می‌کند.
  • ()http.request – یک وهله از کلاس http.ClientRequest بازگشت می‌دهد که استریم نوشتنی است.
  • ()zlib.createGzip – داده‌ها را با استفاده از gzip (یک الگویتم فشرده‌سازی) در یک استریم فشرده می‌کند.
  • ()zlib.createGunzip – یک استریم gzip را از حالت فشرده خارج می‌کند.
  • ()zlib.createDeflate – داده‌ها را با استفاده از deflate که یک الگوریتم فشرده‌سازی است در یک استریم فشرده می‌کند.
  • ()zlib.createInflate – یک استریم deflate را نافشرده می‌کند.

انواع متفاوت استریم‌ها

چهار دسته استریم وجود دارند:

  • Readable – استریمی است که می‌توان از آن pipe کرد، اما نمی‌توان به آن pipe کرد، یعنی می‌توان داده‌ها را از آن دریافت کرد، اما نمی‌توان داده‌هایی به آن ارسال نمود. زمانی که داده‌ها را به یک استریم خواندنی (Readable) ارسال می‌کنید، بافر می‌شود تا این که مخاطب شروع به خواندن داده‌ها بکند.
  • Writable - استریمی است که می‌توان به آن pipe کرد، اما نمی‌توان از آن pipe نمود، یعنی می‌توان داده‌ها را ارسال کرد، اما نمی‌توان دریافت کرد.
  • Duplex – استریمی است که می‌توان هم از آن و هم به آن Pipe کرد و در واقع ترکیبی از دو استریم قبلی محسوب می‌شود.
  • Transform – استریم Transform شبیه به استریم Duplex است، اما خروجی آن به ورودی‌اش تبدیل می‌شود.

ایجاد یک استریم خواندنی

ما استریم Readable را از ماژول stream می‌خوانیم و آن را مقداردهی اولیه می‌کنیم:

1const Stream = require('stream') 
2const readableStream = new Stream.Readable()

اکنون که استریم مقداردهی شد، می‌توانیم داده‌ها را به آن ارسال کنیم:

1readableStream.push('hi!')readableStream.push('ho!')

ایجاد یک استریم نوشتنی

برای ایجاد یک استریم نوشتنی یا writable باید شیء پایه Writable را بسط دهیم و متد ()write_ آن را پیاده‌سازی کنیم. ابتدا یک شیء استریم می‌سازیم:

1const Stream = require('stream')
2const writableStream = new Stream.Writable()

سپس write_ را پیاده‌سازی می‌کنیم:

1writableStream._write = (chunk, encoding, next) => {
2    console.log(chunk.toString()) next()
3}

اکنون می‌توانید یک استریم readable را در مورد زیر pipe کنید:

1process.stdin.pipe(writableStream)

دریافت داده‌ها از استریم خواندنی

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

1const Stream = require('stream')
2const readableStream = new Stream.Readable()
3const writableStream = new Stream.Writable()
4writableStream._write = (chunk, encoding, next) => {
5    console.log(chunk.toString()) next()
6}
7readableStream.pipe(writableStream)
8readableStream.push('hi!') 
9readableStream.push('ho!')

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

1readableStream.on('readable', () => {
2    console.log(readableStream.read())
3})

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

با استفاده از متد ()write می‌توان داده‌ها را به یک استریم نوشتنی ارسال کرد:

1writableStream.write('hey!\n')

علامت دادن به یک استریم نوشتنی در مورد پایان نوشتن

این کار با استفاده از متد ()end انجام می‌یابد:

1const Stream = require('stream')
2const readableStream = new Stream.Readable()
3const writableStream = new Stream.Writable()
4writableStream._write = (chunk, encoding, next) => {
5    console.log(chunk.toString()) next()
6}
7readableStream.pipe(writableStream)
8readableStream.push('hi!') 
9readableStream.push('ho!')
10writableStream.end()

مبانی کار با MySQL در Node.js

MySQL یکی از محبوب‌ترین پایگاه‌های داده رابطه‌ای در دنیا است. اکوسیستم Node.js چند پکیج متفاوت دارد که امکان اینترفیس کردن MySQL، ذخیره‌سازی داده‌ها، بازیابی آن‌ها و مواردی از این دست را در اختیار ما قرار می‌دهند.

ما در این مقاله به معرفی پکیج mysqljs/mysql می‌پردازیم که بیش از 12000 ستاره گیت‌هاب دارد و سال‌ها است که مورد استفاده توسعه‌دهندگان قرار می‌گیرد.

نصب پکیج MySQL در Node.js

با استفاده از دستور زیر می‌توانید پکیج فوق را نصب کنید:

npm install mysql

آغاز اتصال به پایگاه داده

برای ایجاد یک اتصال با پایگاه داده MySQL باید پکیج را در پروژه خود بگنجانید:

1const mysql = require('mysql')

سپس یک اتصال ایجاد می‌کنید:

1const options = {
2    user: 'the_mysql_user_name',
3    password: 'the_mysql_user_password',
4    database: 'the_mysql_database_name'
5}
6const connection = mysql.createConnection(options)

و در ادامه با فراخوانی کد زیر یک اتصال را مقداردهی می‌کنید:

1connection.connect(err => {
2    if (err) {
3        console.error('An error occurred while connecting to the DB') throw err
4    }
5})

گزینه‌های اتصال

در مثال فوق، شیء options شامل 3 گزینه است:

1const options = {
2    user: 'the_mysql_user_name',
3    password: 'the_mysql_user_password',
4    database: 'the_mysql_database_name'
5}

گزینه‌های زیاد دیگری نیز وجود دارد که می‌توان مورد استفاده قرار دارد. فهرست برخی از آن‌ها به صورت زیر است:

  • host – نام میزبان پایگاه داده است که به صورت پیش‌فرض برابر با localhost است.
  • Port – پورت سرور MySQL است که به صورت پیش‌فرض برابر با 3306 است.
  • socketPath – برای تعیین یک سوکت یونیکس به جای هاست و پورت استفاده می‌شود.
  • debug – به طور پیش‌فرض غیرفعال است و می‌تواند برای دیباگ کردن استفاده شود.
  • Trace – به صورت پیش‌فرض فعال است و رد پشته را در زمان بروز خطا نمایش می‌دهد.
  • SSL – برای راه‌اندازی اتصال SSL به سرور استفاده می‌شود.

اجرای کوئری SELECT

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

1connection.query('SELECT * FROM todos', (error, todos, fields) => {
2    if (error) {
3        console.error('An error occurred while executing the query')
4        throw error
5    }
6    console.log(todos)
7})

می‌توانید مقادیری را ارسال کنید که به صورت خودکار escape می‌شوند:

1const id = 223 
2connection.query('SELECT * FROM todos WHERE id = ?', [id], (error, todos, fields) => {
3    if (error) {
4        console.error('An error occurred while executing the query')
5        throw error
6    }
7    console.log(todos)
8})

برای ارسال مقادیر چندگانه کافی است عناصر دیگری در آرایه قرار دهید و به صورت پارامتر دوم ارسال کنید:

1const id = 223
2const author = 'Flavio'
3connection.query('SELECT * FROM todos WHERE id = ? AND author = ?', [id, author], (error, todos, fields) => {
4    if (error) {
5        console.error('An error occurred while executing the query') 
6        throw error
7    }
8    console.log(todos)
9})

اجرای کوئری INSERT

می‌توان یک شیء را در کوئری ارسال کرد:

1const todo = {
2    thing: 'Buy the milk'
3    author: 'Flavio'
4}
5connection.query('INSERT INTO todos SET?', todo, (error, results, fields) => {
6    if (error) {
7        console.error('An error occurred while executing the query') throw error
8    }
9})

اگر جدول دارای کلید اصلی به صورت auto_increment باشد، مقدار آن در results.insertId بازگشت می‌یابد:

1const todo = {
2    thing: 'Buy the milk'
3    author: 'Flavio'
4}
5connection.query('INSERT INTO todos SET?', todo, (error, results, fields) => {
6        if (error) {
7            console.error('An error occurred while executing the query') throw error
8        }
9    }
10    const id = results.resultId console.log(id))

بستن اتصال

زمانی که لازم باشد یک اتصال به پایگاه داده خاتمه یابد می‌توان از متد ()end استفاده کرد:

1connection.end()

بدین ترتیب مطمئن می‌شویم که کوئری‌های در انتظار ارسال می‌شوند و اتصال با ملایمت خاتمه می‌یابد.

تفاوت بین محیط‌های توسعه و پروداکشن

شما می‌توانید پیکربندی‌های مختلفی برای محیط‌های توسعه و پروداکشن انتخاب کنید. Node.js تصور می‌کند که همواره در محیط توسعه اجرا می‌شود. شما می‌توانید با تنظیم متغیر محیطی NODE_ENV=production به Node.js اعلام کنید که در محیط پروداکشن اجرا شده است. این کار عموماً از طریق اجرای دستور زیر در شل (Shell) صورت می‌پذیرد:

export NODE_ENV=production

اما بهتراست آن را در فایل پیکربندی شل خود (مانند فایل.bash_profile در شل Bash) قرار دهید زیرا در غیر این صورت این تنظیمات در حالت راه‌اندازی مجدد سیستم حفظ نخواهند شد.

همچنین می‌توانید متغیر محیطی را از طریق الصاق آن به دستور مقداردهی اولیه اپلیکیشن به کار بگیرید:

NODE_ENV=production node app.js

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

تنظیم محیط روی حالت production به طور کلی موارد زیر را تضمین می‌کند:

  • لاگ کردن در کمترین حد ممکن و فقط در مورد آیتم‌های ضروری صورت می‌گیرد.
  • سطح کش کردن افزایش می‌یابد تا عملکرد بهینه‌ای به دست آید.

برای نمونه کتابخانه قالب‌بندی pug (+) از سوی Express استفاده می‌شود تا در صورتی که NODE_ENV روی production تنظیم نشده باشد، در حالت دیباگ کامپایل شود. نماهای Express در هر درخواست در حالت توسعه کامپایل می‌شوند، در حالی که در حالت پروداکشن کش می‌شوند. مثال‌های زیاد دیگری نیز وجود دارند.

Express قلاب‌های پیکربندی دارد که خاص هر محیط هستند که به صورت خودکار بر مبنای تنظیم مقدار متغیر NODE_ENV فراخوانی می‌شوند:

app.configure('development', () => {//...})app.configure('production', () => {//...})app.configure('production', 'staging', () => {//...})

برای نمونه می‌توانید از این تنظیمات برای تعیین دستگیره‌های خطای مختلف برای حالت‌های متفاوت استفاده کنید:

1app.configure('development', () => {
2    app.use(express.errorHandler({
3        dumpExceptions: true,
4        showStack: true
5    }));
6})
7
8app.configure('production', () => {
9    app.use(express.errorHandler())
10})

سخن پایانی

بدین ترتیب به پایان این بخش از سری مقالات آموزش Node.js می‌رسیم که آخرین بخش نیز محسوب می‌شود. امیدواریم این مقالات توانسته باشند درک مناسبی از محیط زمان اجرای Node.js در اختیار شما قرار دهند و به شما کمک کنند بتوانید پروژه‌های عالی بر مبنای آن‌ها بسازید.

اگر این مطلب برای شما مفید بوده است آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
freecodecamp
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *