خواندن و نوشتن داده‌ها در وب

(اختیاری) نمونه نخستین و آزمایش با Firebase Local Emulator Suite

قبل‌از صحبت درباره نحوه خواندن و نوشتن برنامه‌تان در Realtime Database، مجموعه‌ای از ابزارها را معرفی می‌کنیم که می‌توانید برای نمونه‌سازی و آزمایش عملکرد Realtime Database استفاده کنید: Firebase Local Emulator Suite. اگر درحال امتحان کردن مدل‌های داده مختلف، بهینه‌سازی قوانین امنیتی، یا تلاش برای یافتن مقرون‌به‌صرفه‌ترین روش برای تعامل با پس‌بست هستید، توانایی کار کردن به‌صورت محلی بدون استقرار خدمات زنده می‌تواند ایده بسیار خوبی باشد.

شبیه‌ساز Realtime Database بخشی از Local Emulator Suite است که به برنامه شما امکان می‌دهد با محتوا و پیکربندی پایگاه داده شبیه‌سازی‌شده شما، و همچنین به‌صورت اختیاری با منابع پروژه شبیه‌سازی‌شده شما (توابع، پایگاه‌های داده دیگر، و قوانین امنیتی) تعامل داشته باشد.

استفاده از شبیه‌ساز Realtime Database فقط چند مرحله دارد:

  1. افزودن یک خط کد به پیکربندی آزمایشی برنامه‌تان برای اتصال به شبیه‌ساز.
  2. از ریشه دایرکتوری پروژه محلی، firebase emulators:start را اجرا کنید.
  3. برقراری تماس از کد نمونه اولیه برنامه‌تان بااستفاده از کیت توسعه نرم‌افزار پلاتفرم Realtime Database طبق معمول، یا بااستفاده از REST API‏ Realtime Database.

راهنمای گام‌به‌گام دقیقی شامل Realtime Database و Cloud Functions دردسترس است. همچنین باید نگاهی به Local Emulator Suite مقدمه بیندازید.

دریافت مرجع پایگاه داده

برای خواندن یا نوشتن داده‌ها از پایگاه داده، به نمونه‌ای از firebase.database.Reference نیاز دارید:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

نوشتن داده‌ها

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

داده‌های Firebase با پیوست کردن شنونده ناهمگام به firebase.database.Reference بازیابی می‌شود. شنونده یک‌بار برای وضعیت اولیه داده‌ها و بار دیگر هر زمان که داده‌ها تغییر کنند فعال می‌شود.

عملیات نوشتن پایه

برای عملیات نوشتن پایه، می‌توانید از set() برای ذخیره داده‌ها در یک مرجع مشخص استفاده کنید و هر داده موجود در آن مسیر را جایگزین کنید. برای مثال، یک برنامه وبلاگ‌نویسی اجتماعی ممکن است کاربری را با set() به‌صورت زیر اضافه کند:

Web

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

استفاده از set() داده‌ها را در مکان مشخص‌شده، ازجمله هر گره فرعی، بازنویسی می‌کند.

داده‌ها خوانده شد

گوش دادن به رویدادهای مقدار

برای خواندن داده‌ها در یک مسیر و گوش دادن به تغییرات، از onValue() برای مشاهده رویدادها استفاده کنید. می‌توانید از این رویداد برای خواندن عکس‌های آنی ایستا از محتوا در مسیر مشخص‌شده، همان‌طور که در زمان رویداد وجود داشتند، استفاده کنید. این روش یک‌بار هنگام پیوست شدن شنونده و بار دیگر هربار که داده‌ها، ازجمله فرزندان، تغییر می‌کنند راه‌اندازی می‌شود. فراخوان برگشتی رویداد به یک عکس آنی حاوی همه داده‌های آن مکان، ازجمله داده‌های فرزند، منتقل می‌شود. اگر داده‌ای وجود نداشته باشد، وقتی exists() را فراخوانی می‌کنید، عکس آنی false را برمی‌گرداند و وقتی val() را فراخوانی می‌کنید، null را برمی‌گرداند.

مثال زیر نشان می‌دهد که چگونه یک برنامه وبلاگ‌نویسی اجتماعی تعداد ستاره‌های یک پست را از پایگاه داده بازیابی می‌کند:

Web

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

شنونده یک snapshot دریافت می‌کند که حاوی داده‌ها در مکان مشخص‌شده در پایگاه داده در زمان رویداد است. می‌توانید داده‌ها را در snapshot با روش val() بازیابی کنید.

خواندن داده‌ها برای یک‌بار

خواندن داده‌ها یک‌بار با ()get

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

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

اگر فقط یک‌بار به داده‌ها نیاز دارید، می‌توانید از get() برای دریافت عکس آنی از داده‌های پایگاه داده استفاده کنید. اگر به هر دلیلی get() نتواند مقدار سرور را برگرداند، کارخواه حافظه نهان فضای ذخیره‌سازی محلی را کاوش می‌کند و اگر مقدار همچنان پیدا نشود، خطا برمی‌گرداند.

استفاده غیرضروری از get() می‌تواند استفاده از پهنای باند را افزایش دهد و منجر به ازدست رفتن عملکرد شود، که می‌توان بااستفاده از شنونده هم‌زمان همان‌طور که در بالا نشان داده شده است از آن جلوگیری کرد.

Web

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

خواندن داده‌ها یک‌بار با ناظر

در برخی موارد ممکن است بخواهید مقدار از حافظه نهان محلی فوراً برگردانده شود، به‌جای اینکه مقدار به‌روزشده در سرور بررسی شود. در این موارد، می‌توانید از once() استفاده کنید تا داده‌ها را فوراً از حافظه نهان دیسک محلی دریافت کنید.

این روش برای داده‌هایی مفید است که فقط یک‌بار باید بار شوند و انتظار نمی‌رود که به‌طور مکرر تغییر کنند یا به گوش دادن فعال نیاز داشته باشند. برای مثال، برنامه وبلاگ‌نویسی در مثال‌های قبلی از این روش برای بار کردن نمایه کاربر هنگام شروع نوشتن پست جدید استفاده می‌کند:

Web

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

به‌روزرسانی یا حذف داده‌ها

به‌روزرسانی فیلدهای خاص

برای نوشتن هم‌زمان در فرزندان خاص یک گره بدون بازنویسی گره‌های فرزند دیگر، از روش update() استفاده کنید.

هنگام فراخوانی update()، می‌توانید مقادیر فرزند سطح پایین‌تر را با مشخص کردن مسیر کلید به‌روز کنید. اگر داده‌ها در چندین مکان ذخیره شده‌اند تا مقیاس‌بندی بهتر انجام شود، می‌توانید همه نمونه‌های آن داده‌ها را بااستفاده از پخش داده به‌روز کنید.

برای مثال، یک برنامه وبلاگ‌نویسی اجتماعی ممکن است پستی ایجاد کند و هم‌زمان آن را در فید فعالیت‌های اخیر و فید فعالیت‌های کاربر پست‌کننده بااستفاده از کدی مانند این به‌روز کند:

Web

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

این مثال از push() برای ایجاد پست در گره حاوی پست‌های همه کاربران در /posts/$postid و هم‌زمان بازیابی کلید استفاده می‌کند. سپس می‌توان از این کلید برای ایجاد ورودی دوم در پست‌های کاربر در /user-posts/$userid/$postid استفاده کرد.

بااستفاده از این مسیرها، می‌توانید به‌روزرسانی‌های هم‌زمان را در چندین مکان در درخت JSON با یک فراخوانی به update() انجام دهید، مانند نحوه ایجاد پست جدید در هر دو مکان در این مثال. به‌روزرسانی‌های هم‌زمان که به این روش انجام می‌شوند اتمی هستند: یا همه به‌روزرسانی‌ها موفق می‌شوند یا همه به‌روزرسانی‌ها ناموفق می‌شوند.

افزودن فراخوان تکمیل

اگر می‌خواهید بدانید داده‌هایتان چه زمانی ثبت شده است، می‌توانید فراخوان تکمیل اضافه کنید. هر دو set() و update() یک فراخوان برگشتی تکمیل اختیاری را می‌پذیرند که وقتی نوشتن در پایگاه داده ثبت می‌شود، فراخوانی می‌شود. اگر فراخوانی ناموفق باشد، فراخوان برگشتی یک شیء خطا را ارسال می‌کند که دلیل ناموفق بودن را نشان می‌دهد.

Web

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

حذف کردن داده‌ها

ساده‌ترین راه برای حذف داده‌ها، فراخوانی remove() در ارجاع به مکان آن داده‌ها است.

همچنین می‌توانید با مشخص کردن null به‌عنوان مقدار برای عملیات نوشتن دیگری مانند set() یا update()، حذف کنید. می‌توانید از این تکنیک با update() برای حذف چند فرزند در یک فراخوانی میانای برنامه‌سازی کاربردی استفاده کنید.

دریافت Promise

برای اینکه بدانید داده‌هایتان چه زمانی به سرور Firebase Realtime Database ارسال می‌شود، می‌توانید از Promise استفاده کنید. ‫set() و update() می‌توانند Promise را برگردانند که می‌توانید از آن برای دانستن زمان ثبت نوشتن در پایگاه داده استفاده کنید.

جدا کردن شنونده‌ها

با فراخوانی روش off() در مرجع پایگاه داده Firebase، فراخوانی‌های برگشتی برداشته می‌شوند.

می‌توانید یک شنونده را با ارسال آن به‌عنوان پارامتر به off() حذف کنید. فراخوانی off() در مکان بدون آرگومان، همه شنوندگان را در آن مکان برمی‌دارد.

فراخوانی off() در شنونده والد به‌طور خودکار شنونده‌های ثبت‌شده در گره‌های فرزند آن را حذف نمی‌کند؛ off() باید در هر شنونده فرزند نیز فراخوانی شود تا فراخوان برگشتی حذف شود.

ذخیره داده‌ها به‌عنوان تراکنش

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

برای مثال، در نمونه برنامه وبلاگ اجتماعی، می‌توانید به کاربران اجازه دهید پست‌ها را ستاره‌دار و بی‌ستاره کنند و تعداد ستاره‌هایی را که پست دریافت کرده است به‌صورت زیر پیگیری کنید:

Web

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

استفاده از تراکنش مانع از نادرست بودن تعداد ستاره‌ها می‌شود، اگر چند کاربر به‌طور هم‌زمان به یک پست ستاره دهند یا کارخواه داده‌های قدیمی داشته باشد. اگر تراکنش رد شود، سرور مقدار فعلی را به کارخواه برمی‌گرداند و کارخواه تراکنش را دوباره با مقدار به‌روزشده اجرا می‌کند. این کار تا زمانی که تراکنش پذیرفته شود یا تراکنش را لغو کنید تکرار می‌شود.

افزایش اتمی سمت سرور

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

Web

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

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

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

کار کردن با داده‌ها به‌صورت آفلاین

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

هر کارخواه متصل به پایگاه داده Firebase نسخه داخلی خودش را از هر داده فعالی حفظ می‌کند. وقتی داده‌ها نوشته می‌شوند، ابتدا در این نسخه محلی نوشته می‌شوند. سپس کارخواه Firebase آن داده‌ها را با سرورهای پایگاه داده ازراه‌دور و با کارخواه‌های دیگر براساس «بهترین تلاش» همگام‌سازی می‌کند.

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

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

در درباره قابلیت‌های آنلاین و آفلاین بیشتر بدانید.. بیشتر درباره رفتار آفلاین صحبت خواهیم کرد.

مراحل بعدی