موقعیتهای جغرافیایی به صورت یک API ارائه شدهاند که متدهای مختلفی برای استفاده در یک وب اپلیکیشن دارند. به طور مشابه، React Native از این API بهره میگیرد و در آن به صورت polyfill ارائه شده است. موقعیت جغرافیایی قابلیتی ضروری برای اپلیکیشنهای موبایل محسوب میشود. برخی از اپلیکیشنهای مشهور موبایل که برای اغلب کارکردهای خود از آن استفاده میکنند، شامل گوگل مپس، اوبر و… هستند. در این مقاله، با دو روش مختلف برای یکپارچهسازی API مختصات جغرافیایی در یک اپلیکیشن React Native آشنا میشویم. این کار با استفاده از Expo و همچنین از طریق react-native-cli اجرا خواهد شد. همچنین با روش درخواست مجوزهای اپلیکیشن آشنا میشویم.
در همین راستا قصد داریم یک قابلیت آنی را پیادهسازی کنیم که در این نوع اپلیکیشنها به صورت متداول استفاده میشود و آن درخواست «مجوزهای کاربر» (USER Permissions) است. درخواست مجوز در react-native-cli ممکن است کمی پیچیده باشد، اما پس از خواندن این مقاله مطمئناً پیادهسازی آن برای شما آسانتر خواهد بود.
ما در این مقاله از expo-cli استفاده میکنیم. با اجرای دستورهای زیر میتوانید یک پروژه Expo را پیکربندی کرده و راهاندازی کنید.
npm install -g expo-cli expo-cli init find-me # select blank template & traverse into a newly created directory npm run ios # for Window users، run npm run android
در این مرحله با صفحه خوشامدگویی مواجه میشوید. ما کار خود را از همین جا آغاز میکنیم و ابتدا فایل App.js را ویرایش میکنیم.
یک فایل جدید برای کامپوننت FindMe در مسیر src -> screens -> FindMe -> index.js ایجاد میکنیم. درون این فایل صرفاً یک متن را نمایش خواهیم داد.
بدین ترتیب اپلیکیشن ما اکنون اینگونه به نظر میرسد:

API مربوط به Geolocation به صورت یک شیء سراسری به نام navigator در React Native حضور دارد و این وضعیت مشابه وب است. این API از طریق navigator.geolocation در کد منبع ما قابل دسترسی است و نیازی به ایمپورت کردن آن وجود ندارد.
ما در این مقاله با توجه به مقاصد آموزشی خود، از متد getCurrentPosition از API مربوط به Geolocation استفاده میکنیم. این متد به اپلیکیشن موبایل امکان میدهد که مکان کاربر را درخواست کند و سه پارامتر به صورت callback موفقیت، callback شکست و یک شیء پیکربندی نیز میپذیرد.
نخستین callback یک آرگومان position دارد که شیئی با مشخصههای زیر است:
اکنون این قابلیت را در کامپوننت FindME پیادهسازی میکنیم:
کار خود را با ایمپورت کردن TouchableOpcaity آغاز میکنیم. این یک پوشش است که به طور صحیحی به لمسهای کاربر پاسخ میدهد. در یک اپلیکیشن موبایل میتوان از این موارد بهرهبرداری کرد. آن را میتوان مانند یک دکمه در وب اپلیکیشن تصور کرد. این پوشش اخیراً ایمپورت شده یک prop به نام onPress میپذیرد که از آن برای فراخوانی تابعی که مانند مقدار تعریف شده استفاده خواهد شد. در این مثال نام آن findCurrentLocation است.
تابع findCurrentLocation منطق مربوط به واکشی مکان کاربر را نگهداری میکند. همچنین از حالت محلی برای نمایش مختصات دریافتی از شیء position استفاده میکنیم. متن Where Am I اکنون قابل کلیک کردن است.

تا به اینجا بخش اپلیکیشن به پایان رسیده است. اینک نوبت آن رسیده است که با شیوه افزودن مجوزها به اپلیکیشن آشنا شویم.
در زمان ارسال درخواست برای دریافت اطلاعات یک کاربر چه مکان باشد و چه هر اطلاعات حساس دیگر روی دستگاه، شما به عنوان توسعهدهنده موظف هستید ابتدا درخواست مجوز بکنید. این فرایند چه در زمان توسعه اپلیکیشن و چه در زمان استفاده از اپلیکیشن یک بار رخ میدهد. اغلب دسترسیها زمانی درخواست میشوند که کاربر اپلیکیشن را برای اولین بار اجرا میکند.
Expo همه API-های دسترسی که برای این اپلیکیشن دمو یا هر اپلیکیشن دیگری که با Expo ساخته میشود را یکپارچهسازی کرده است. این API متدهای مختلفی برای انواع دستگاهها که ممکن است نیاز به درخواست مجوز داشته باشند را شامل میشود. این مجوز دسترسیها میتوانند شامل مکان دوربین، ضبط صدا، مخاطبین، گالری تصاویر، تقویم، یادآوریها (فقط iOS) و اعلانها باشد. ما قصد داریم از Location استفاده کنیم.
در این مرحله ما «حالت» (State) خود را کمی تغییر میدهیم. بدین ترتیب دیگر کل شیء geolocation و errorMessage را در صوت بروز خطا نگهداری نخواهد کرد. findCurrentLocation بدون تغییر باقی میماند. در واقع ما از آن استفاده نخواهیم کرد. Expo متدی دارد که این کار را برای ما انجام میدهد. این متد getCurrentPositionAsync نام دارد. این متد صرفاً مکان کاربر و مشخصههای دیگر ارائه شد از سوی getCurrentPosition را در صورتی که مجوز اعطا شده باشد، واکشی میکند. در متد رندر prop به نام onPress یک متد دیگر را فراخوانی میکند که findCurrentLocationAsync نام دارد و منطق مربوط به درخواست مجوز و واکشی دادههای مکان را پس از این که دسترسی کاربر اعطا شد اجرا میکند. اگر مجوز ارائه نشده باشد، پیام خطایی صادر میشود و در غیر این صورت مکان بهروزرسانی خواهد شد.
آخرین گام مربوط به کاربران اندروید است. فایل app.json را باز کنید و به بخش permissions بروید.

اگر دکمه Allow فشرده شود، نتیجه زیر عاید میشود:

توجه کنید که در حالت توسعه و اجرا کردن اپلیکیشن در شبیهساز، دسترسیها تنها یک بار تقاضا میشوند. برای اجرای مجدد آن، باید اپلیکیشن را از شبیهساز حذف کرده و دستور شروع اپلیکیشن Expo را مجدداً بدهید.
کد کامل این اپلیکیشن را از این آدرس (+) دانلود کنید.
استفاده از react-native-cli به این معنی است که باید مجوزها را خودتان تنظیم کنید، با این حال، منطق دریافت مکان کاربر همان خواهد بود.
npm install -g react-native-cli react-native init findCoordsApp
هیچ قالبی در react-native-cli وجود ندارد از این رو زمانی که دایرکتوری ایجاد شود، آن را بررسی کرده و دستور npm start را اجرا کنید تا ببینید آیا همه چیز به درستی نصب شده است یا نه. زمانی که این پروژه را در یک IDE یا ویرایشگر کد باز کنید، نخستین چیزی که متوجه خواهید شد این است که تغییرهای زیادی در ساختار فایلها و پوشهها وجود دارد. Expo در مقایسه با این روش ساختار پروژه کوچکتری دارد. پوشههای مختلف build مانند /android و /ios برای هر پلتفرم وجود دارند. همچنین میتوانید از flow استفاده کنید که مشابه TypeScript است و از سوی فیسبوک به صورت متنباز عرضه شده است.

ما صرفاً فایل App.js را با درج کد زیر اصلاح میکنیم:
توجه داشته باشید که findCoordinates به روشی مانند اپلیکیشن Expo عمل میکند و ضمناً کد موجود در تابع ()render دقیقاً همان است. گام بعدی ما تنظیم مجوزها است.
موقعیت جغرافیایی در iOS به صورت پیشفرض در زمان ایجاد پروژه با استفاده از react-native-cli فعال شده است. برای استفاده از آن کافی است یک کلید را در info.plist قرار دهید که درون دایرکتوری ios/findCoordsApp قرار دارد.
برای اندروید باید خط زیر را در فایل android/app/src/AndroidManifest.xml اضافه کنیم:
اکنون اگر اپلیکیشن را اجرا کنید با صفحه زیر مواجه میشوید:

روی متن کلیک کنید تا از شما سؤال شود آیا میخواهید به اپلیکیشن دسترسی به مکان بدهید یا نه. ما به منظور نمایش موضوع، از یک شبیهساز اندروید استفاده میکنیم، چون قبلاً در بخش Expo دیدیم که روی شبیهساز iOS چگونه عمل میکند.

اگر دکمه Allow را بزنید، نتیجه زیر حاصل میشود:

کد کامل این اپلیکیشن را در این ریپازیتوری گیتهاب (+) ملاحظه کنید. اگر قصد دارید در مورد API موقعیت جغرافیایی در اپلیکیشنهای ریاکت نیتیو بیشتر بدانید به مستندات رسمی (+) مراجعه کنید.
منبع: فردارس
ما اسکریپتهای SQL خود را ایجاد خواهیم کرد که شامل آن دسته از گزارههای SQL هستند که میخواهیم روی پایگاه داده خود اجرا کنیم. یک دایرکتوری ایجاد میکنیم که در آن کار خواهیم کرد. ضمناً یک دایرکتوری فرعی ایجاد میکنیم که اسکریپتهای sql. را در آن ذخیره میکنیم.
mkdir -p ~/my-mysql/sql-scripts $ cd ~/my-mysql/sql-scripts
ما میخواهیم پایگاه داده خود را با یک جدول به نام «کارمندان» (employees) سفارشیسازی کنیم. جدول باید شامل یک ردیف با یک کارمند و ستونهای نام، نام خانوادگی، دپارتمان و ایمیل (first name ،last name ،department و email) باشد.
یک فایل به نام CreateTable.sql بسازید. این فایل شامل گزاره SQL برای ایجاد جدولی به نام employees خواهد بود. ما میخواهیم چهار ستون به جدول اضافه کنیم.
یک فایل به نام InsertData.sql بسازید. این فایل شامل گزاره ما برای درج دادهها در جدول employees است.
دستور tree را اجرا کنید تا تأیید شود که دو اسکریپت وجود دارد و در دایرکتوری درستی ذخیره شده است.
اکنون که اسکریپتها آماده هستند، میتوانیم یک Dockerfile بنویسیم تا ایمیج داکر خود را بر مبنای ایمیج رسمی MySQL (+) بسازیم.
cd ~/my-mysql/ $ vi Dockerfile
محتوای داکرفایل به صورت زیر است:
# Derived from official mysql image (our base image) FROM mysql # Add a database ENV MYSQL_DATABASE company # Add the content of the sql-scripts/ directory to your image # All scripts in docker-entrypoint-initdb.d/ are automatically # executed during container startup COPY./sql-scripts/ /docker-entrypoint-initdb.d/
ایمیج داکر خود را بسازید:
cd ~/my-mysql/ $ docker build -t my-mysql. Sending build context to Docker daemon 4.608kB Step 1/2: FROM mysql latest: Pulling from library/mysql Digest: sha256:691c55aabb3c4e3b89b953dd2f022f7ea845e5443954767d321d5f5fa394e28c Status: Downloaded newer image for mysql:latest ---> 5195076672a7 Step 2/2: ADD sql-scripts/ /docker-entrypoint-initdb.d/ ---> 25065c3d93c0 Successfully built 25065c3d93c0 Successfully tagged my-mysql:latest
و کانتینر MySQL خود را از ایمیج بسازید:
docker run -d -p 3306:3306 --name my-mysql \ -e MYSQL_ROOT_PASSWORD=supersecret my-mysql
اینک میتوانیم روال کار را تأیید کنیم. درون کانتینر از exec استفاده خواهیم کرد:
چنان که میبینید اسکریپت کار میکند. بدین ترتیب ما ایمیج داکر پایگاه داده MySQL سفارشی خودمان را داریم. این راهحلی عالی برای توسعه محلی بین چند توسعهدهنده است. با اشترک ایمیج داکر هر توسعهدهنده میتواند با شروع کانتینر از ایمیج پایگاه داده مربوطه استفاده کند.
با این وجود، لازم به اشاره است که این راهحل همواره هم بهترین راهحل نیست:
به همین دلیل است که روش دیگری برای سفارشیسازی MySQL داکر نیز وجود دارد.
در بخش آخر، اسکریپتها را درون کانتینر رسمی داکر MySQL سوار میکنیم.
docker run -d -p 3306:3306 --name my-mysql \ -v ~/my-mysql/sql-scripts:/docker-entrypoint-initdb.d/ \ -e MYSQL_ROOT_PASSWORD=supersecret \ -e MYSQL_DATABASE=company \ Mysql
بدین ترتیب بار دیگر اسکریپت تأیید میشود. از همان مراحل که قبلاً اجرا کردیم استفاده میکنیم: exec درون کانتینر استفاده میشود تا وجود جدول و دادهها بررسی شود.
این روش انعطافپذیر است، اما توزیع آن در میان توسعهدهندهها کمی دشوارتر خواهد بود. همه توسعهدهندهها باید اسکریپتها را در دایرکتوری خاصی در سیستم محلی خود ذخیره کنند و باید در زمان اجرای دستور docker run به یک دایرکتوری خاص اشاره کنند..
منبع: فرادرس
در این مقاله به اختصار به بررسی مشکلات مرتبط با جاوا اسکریپت ناهمگام میپردازیم. همچنین برخی از تکنیکهای مختلف برنامهنویسی ناهمگام که میتوان مورد استفاده قرار دارد را بررسی میکنیم و نشان میدهیم که این تکنیکها چگونه میتوانند به حل برخی از مسائل کمک کنند. برای مطالعه بخش قبلی به لینک زیر رجوع کنید.
هدف از این مقاله آشنا ساختن مخاطب با جاوا اسکریپت ناهمگام، تفاوت آن با جاوا اسکریپت همگام و کاربردهای آن است.
برای این که بتوانیم معنی جاوا اسکریپت «ناهمگام» (Asynchronous) را بدانیم باید ابتدا مطمئن شویم که معنی جاوا اسکریپت «همگام» (Synchronous) را میدانیم. در این بخش برخی از اطلاعاتی که در مقاله قبلی این سری ارائه شده است را جمعبندی میکنیم.
بخش عمدهای از کارکردهایی که در بخش قبلی این سری آموزشی مشاهده کردیم در واقع تکنیکهای برنامهنویسی همگام بودند. در این روش شما کد اجرا میشود و نتیجه به محض این که مرورگر بتواند کد را اجرا کند بازگشت مییابد. به مثال ساده زیر توجه کنید:
در این مثال یک دکمه وجود دارد که پس از یک میلیون بار محاسبه تاریخ، عبارتی را روی صفحه نمایش میدهد. آن را عملاً در این آدرس (+) مشاهده کنید.
در بلوک کد فوق کارهای زیر یکی پس از دیگری اجرا میشوند:
زمانی که هر کدام از این عملیات در حال پردازش هستند، هیچ کار دیگری اجرا نخواهد شد و به این ترتیب رندر کردن صفحه متوقف میشود. دلیل این مسئله را در بخش قبلی این سری مقالات گفتیم و این است که جاوا اسکریپت یک زبان تک نخی است. در هر زمان تنها یک کار میتواند اجرا شود که روی نخ main اجرا میشود و هر چیز دیگری تا زمان تکمیل شدن آن عملیات متوقف خواهد بود.
بنابراین در مثال فوق، پس از این که روی دکمه کلیک کردید، پاراگراف ظاهر نمیشود تا این که دکمه OK را در کادر هشدار کلیک کنید. میتوانید آن را خودتان بررسی کنید:
نکته: به خاطر داشته باشید که ()alert گرچه برای نمایش عملیات انسداد همگام مفید است، اما استفاده از آن در اپلیکیشنهای واقعی فاجعه محسوب میشود.

به دلایلی که در بخش قبلی نشان دادیم، بسیاری از قابلیتهای API وب به خصوص آنهایی که به نوعی منابع دسترسی مییابند یا آن را از یک محل خارجی واکشی میکنند، هم اینک از کدنویسی ناهمگام برای اجرا استفاده میکنند. برای نمونه واکشی فایل از شبکه یا دسترس به پایگاه داده و بازگشت داده از آن، دسترسی به استریم ویدئو از یک دوربین وب یا پخش تصاویر نمایشگر روی یک هدست VR شامل این عملیات میشود.
چرا عادت به کار با کد ناهمگام دشوار است؟ برای پاسخ به این سؤال یک مثال ساده را بررسی میکنیم. وقتی یک تصویر را از سرور واکشی میکنید، نمیتوانید بیدرنگ نتیجهای را بازگشت دهید. این بدان معنی است که شِبه کد زیر عملی نخواهد بود:
دلیل این مسئله آن است که نمیدانید چه قدر طول میکشد تا تصویر دانلود شود و از این رو زمانی که خط دوم اجرا شود (به احتمال زیاد در تمام موارد) خطایی ایجاد میشود، چون response هنوز موجود نیست. به جای آن باید از کد خود بخواهید صبر کند تا response بازگشت یابد و سپس تلاش کند تا هر کاری دیگری با آن انجام دهد.
دو نوع عمده از سبک کد ناهمگام وجود دارد که در جاوا اسکریپت با آن مواجه میشویم. یکی Callback-های سبک قدیمی است و دیگری کد به سبک Promise. در ادامه این مقاله هر کدام از آنها را به نوبت بررسی میکنیم.
Callback-های ناهمگام تابعهایی هستند که در زمان فراخوانی یک تابع به صورت پارامتر استفاده میشوند و شروع به اجرا در پس زمینه میکنند. زمانی که کد پس زمینه اجرای خود را تمام کند، تابع Callback را فراخوانی میکند تا بداند که کار انجام یافته است و یا اطلاع دهد که اتفاق خاصی رخ داده است. استفاده از Callback روشی نسبتاً قدیمی محسوب میشود، گرچه همچنان در برخی API-های قدیمیتر اما رایج استفاده میشود.
نمونهای از یک Callback ناهمگام، پارامتر دوم ()addEventListener است که در بخش قبلی دیدیم:
پارامتر نخست نوع رویدادی است که منتظر وقوع آن هستیم و پارامتر دوم یک تابع Callback است که در زمان اجرای رویداد احضار میشود.
زمانی که یک تابع Callback را به صورت یک پارامتر به تابع دیگری ارسال میکنیم، صرفاً تعریف تابع را به صورت پارامتر میفرستیم و تابع Callback بیدرنگ اجرا نخواهد شد. نام آن Callback یعنی فراخوانی بازگشتی است و برحسب نام خود به صورت ناهمگام جایی در بدنه تابع قرار گرفته و در زمان مقتضی اجرا خواهد شد. تابعی که Callback را در خود احاطه کرده، مسئول اجرای تابع Callback در زمان مقتضی است.
شما میتوانید تابعهای خاصی را بنویسید که شامل Callback باشند. در مثال زیر یک تابع Callback را میبینیم که منبعی را از طریق API به نام XMLHttpRequest بارگذاری میکند:
ما در این کد یک تابع به نام ()displayImage ایجاد کردهایم که یک blob ارسالی را به صورت یک URL شیء را نمایش میدهد و سپس یک تصویر ایجاد میکند تا URL را در آن نمایش دهد و آن را به <body> سند الصاق میکند.
با این حال، در ادامه یک تابع ()loadAsset ایجاد میکنیم که یک Callback به عنوان پارامتر میگیرد و همراه با آن یک URL برای واکشی و نوع محتوا را نیز دریافت میکند. این تابع از XMLHttpRequest که عموماً به اختصار XHR نامیده میشود، برای واکشی منبع از URL مفروض استفاده میکند و سپس پاسخ را در Callback ارسال میکند تا هر کاری که لازم است روی آن اجرا کند. در این حالت، callback روی درخواست XHR منتظر میماند تا دانلود کردن منبع به پایان برسد. این کار با استفاده از دستگیره رویداد onload صورت میپذیرد و سپس تصویر را به Callback ارسال میکند.
Callback-ها متنوع هستند و نه تنها امکان کنترل ترتیب اجرای تابعها و این که چه دادههایی به آنها ارسال میشوند را دارند، بلکه امکان فرستادن دادهها به تابعهای مختلف بر اساس شرایط خاص را نیز فراهم میسازند. بنابراین میتوانید کارهای مختلفی مانند ()processJSON() ،displayText و غیره برای اجرا روی یک پاسخ دانلود شده تعریف کرد.
توجه داشته باشید که همه Callback-ها ناهمگام نیستند و برخی از آنها به صورت همگام اجرا میشوند. به عنوان مثال، زمانی که از ()Array.prototype.forEach برای تعریف حلقه روی آیتمهای یک آرایه استفاده میکنید، در واقع از یک Callback همگام استفاده کردهاید.
در این مثال، روی یک آرایه متشکل از نام خدایان یونان باستان حلقهای تعریف کردیم و شماره اندیس و مقدار آنها را در کنسول نمایش میدهیم. پارامتر مورد انتظار ()forEach یک تابع Callback است که خودش دو پارامتر میگیرد که یکی ارجاعی به نام آرایه و دیگری مقدار اندیسهاست. با این حال این تابع منتظر چیزی نمیماند و بیدرنگ اجرا میشوند.
Promise-ها سبک جدیدی از کد ناهمگام هستند که در API-های مدرن وب مشاهده میشوند. مثال خوبی از آن در API به نام ()fetch دیده میشود که اساساً نسخه مدرنتر و کارآمدتری از XMLHttpRequest است. در ادامه مثال کوچکی را ملاحظه میکنید که دادهها را از سرور واکشی میکند:
در کد فوق ()fetch یک پارامتر منفرد میگیرد که URL منبعی است که میخواهیم از شبکه واکشی کنیم و یک Promise بازگشت میدهد. Promise شیئی است که تکمیل یا شکست عملیات ناهمگام را نمایش میدهد. این پارامتر یک حالت واسط را نمایش میدهد. در واقع این روشی است که مرورگر استفاده میکند تا اعلام کند: «من قول میدهم به زودی با پاسخ بازخواهم گشت» و از این رو نام آن Promise یعنی «قول» است.
عادت به استفاده از این مفهوم نیاز به تمرین کردن دارد. در عمل کمی شبیه به گربه شرودینگر است. هیچ کدام از وضعیتها هنوز اتفاق نیفتادهاند و از این رو عملیات واکشی در حال حاضر منتظر نتیجه عملیات مرورگر است تا عملیات خود را در زمانی در آینده به پایان ببرد. در ادامه سه بلوک کد دیگر نیز داریم که در انتهای ()fetch قرار دارند:
در بخشهای بعدی این سری مقالات آموزشی با Promise-ها بیشتر آشنا خواهید شد و اگر اکنون نکته مبهمی برایتان وجود دارد لازم نیست نگران باشید. کافی است به ادامه مطالعه مقالههای این سری بپردازید.
عملیات ناهمگام مانند Promise در یک «صف رویداد» (event queue) قرار میگیرد که پس از پایان پردازش نخ اصلی اجرا میشود، به طوری که کد جاوا اسکریپت بعدی را مسدود نمیکند. عملیات صفبندی شده به محض این که امکانپذیر باشد، اجرا میشوند و نتایج آنها در محیط جاوا اسکریپت بازگشت مییابد.
Promise-ها مشابهتهایی با Callback های سبک قدیم دارند. آنها اساساً یک شیء را بازگشت میدهند که به جای الزام به ارسال Callback به یک تابع، به تابعهای Callback الصاق مییابند.
با این حال، Promise-ها به طور اختصاص برای مدیریت عملیات ناهمگام ساخته شدهاند و مزیتهای زیادی نسبت به Callback-های قدیمی دارند که در فهرست زیر به برخی از آنها اشاره کردهایم:
در ادامه مثال دیگری را بررسی میکنیم که ماهیت کد ناهمگام را بیشتر روشن میسازد و نشان میدهد که وقتی از ترتیب اجرای کد به درستی آگاه نباشیم و تلاش کنیم با کد ناهمگام همانند کد همگام برخورد کنیم، چه نوع مشکلاتی میتوانند بروز یابند. مثال زیر تا حدودی مشابه آن چیزی است که قبلاً دیدیم. یک تفاوت آن است که ما در این کد تعدادی گزاره ()console.log نیز گنجاندهایم که ترتیب کدی که شما ممکن است فکر کنید اجرا میشود را نمایش میدهد.
مرورگر کار خود را با اجرای کد آغاز میکند و نخستین گزاره ()console.log یعنی پیام «Starting» را میبینید و آن را اجرا میکند، سپس متغیر image را ایجاد میکند.
در ادامه به خط بعدی میرود و شروع به اجرای بلوک ()fetch میکند، اما از آنجا که ()fetch به صورت ناهمگام بدون مسدودسازی اجرا میشود، اجرای کد پس از کد مبتنی بر Promise ادامه مییابد و بدین طریق به گزاره ()console.log نهایی میرسد و خروجی یعنی پیام «!All done» را در کنسول ارائه میکند.
تنها زمانی که بلوک ()fetch به صورت کامل پایان یابد و نتیجهاش را از طریق بلوکهای ()then. ارائه کند، در نهایت پیام ()console.log دوم یعنی «(;It worked» ظاهر میشود. بنابراین پیامها در ترتیبی متفاوت از آن چه احتمالاً انتظار داشتید ظاهر میشوند:
اگر این وضعیت موجب سردرگمی شما شده است، مثال کوچکتر زیر را در نظر بگیرید:
رفتار این بلوک کد کاملاً مشابه است، پیامهای اول و سوم ()console.log بیدرنگ نمایش پیدا میکنند، اما گزاره دوم مسدود میشود تا این که دکمه ماوس را کلیک کنید. مثال قبلی نیز به روش مشابهی عمل میکند به جز این که به جای کلیک کردن ماوس، کد پیام دوم مسدود میشود تا زنجیره Promise منبعی را واکشی کرده و آن را روی صفحه نمایش دهد.
در یک مثال از کدی که پیچیدهتر از این دو است، این تنظیمات میتواند موجب بروز مشکل شود، چون نمیتوان هیچ بلوک کد ناهمگامی را که نتیجهای بازگشت میدهد و نتیجه آن در ادامه مورد نیاز خواهد بود در بلوک کد ناهمگام گنجاند. نمیتوان تضمین کرد که تابع ناهمگام پیش از این که مرورگر بلوک همگام را پردازش بکند بازگشت خواهد یافت.
برای این که این مشکل را در عمل مشاهده کنیم ابتدا یک کپی از کد زیر روی سیستم خود ایجاد کنید:
سپس فراخوانی ()console.log را به صورت زیر تغییر دهید:
اینک باید به جای پیام سوم، یک خطا در کنسول مشاهده کنید:
TypeError: image is undefined; can't access its "src" property
دلیل این امر آن است که مرورگر تلاش میکند، گزاره ()console.log سوم را اجرا کند و بلوک ()fetch هنوز اجرای خود را تمام نکرده است و از این رو متغیر image هنوز مقداری ندارد.

برای حل مشکلی که در مثال ()fetch دیدیم و برای این که گزاره ()console.log سوم در ترتیب مطلوب نمایش پیدا کند، باید کاری کنیم که گزاره ()console.log سوم نیز به صورت ناهمگام اجرا شود. این کار از طریق انتقال آن به درون بلوک ()then. که به انتهای دومی زنجیر شده است، امکانپذیر خواهد بود. همچنین میتواند به سادگی آن را به درون بلوک ()then سوم برد. تلاش کنید این مشکل را به این ترتیب اصلاح کنید.
نکته: اگر با مشکل مواجه شدید میتوانید از کد زیر کمک بگیرید:
جاوا اسکریپت در ابتداییترین شکل خود یک زبان برنامهنویسی همگام، مسدودکننده و تک نخی است بدین معنی که در این زبان در هر لحظه تنها یک عملیات اجرا میشود. اما مرورگرهای وب تابعها و API-هایی تعریف میکنند که امکان ثبت تابعهایی که باید به صورت ناهمگام اجرا شوند را میدهند. بدین ترتیب میتواند این تابعها را به صورت ناهمگام در زمانی که رویداد خاصی اتفاق افتاد مانند گذشت زمان معین، تعامل کاربر با ماوس یا رسیدن دادهای از شبکه، اجرا کرد. این به آن معنی است که میتوان اجازه داد کد چندین کار را همزمان اجرا کند و در عین حال نخ اصلی نیز مسدود یا متوقف نشود.
این که بخواهیم کد خود را به صورت ناهمگام و یا همگام اجرا کنیم به کاری که قرار است انجام دهیم وابسته است. مواردی وجود دارند که میخواهیم چیزی بیدرنگ بارگذاری و اجرا شود. برای نمونه زمانی که نوعی استایل تعریف شده از سوی کاربر را روی یک صفحه وب اعمال میکنیم میخواهیم که این کار در سریعترین حالت ممکن اجرا شود.
اما اگر عملیاتی اجرا میکنیم که زمانبر خواهد بود مثلاً به پایگاه داده کوئری میزنیم و از نتایج آن برای ایجاد قالب استفاده میکنیم بهتر است آن را از نخ اصلی خارج کنیم و این کار را به صوت ناهمگام به پایان ببریم. شما در طی زمان خواهید آموخت که چه زمانی باید از تکنیکهای ناهمگام و چه هنگام از کدهای همگام استفاده کنید. برای مطالعه بخش بعدی این مطلب به لینک زیر رجوع کنید:
منبع: فرادرس
اگر با زبانهایی مانند جاوا یا Node.js آشنایی دارید، در این صورت احتمالاً با مفهوم پکیجها کاملاً آشنا هستید. پکیج چیزی به جز یک دایرکتوری با تعدادی فایلهای کد نیست که هر کدام متغیرها (قابلیتهای) مختلفی را از یک نقطه مرجع واحد عرضه میکنند. در ادامه به توضیح مفاهیم مرتبط با پکیج های Go میپردازیم.
تصور کنید بیش از هزار تابع دارید که در زمان کار روی هر پروژهای به طور مداوم به آنها نیاز پیدا میکنید. برخی از این تابعها رفتار مشترکی دارند. برای نمونه تابع toUpperCase و toLowerCase حالت حروف یک رشته را تغییر میدهند، بنابراین آنها را در یک فایل منفرد مثلاً با نام case.go مینویسید. تابعهای دیگری نیز وجود دارند که عملیات دیگری روی نوع داده String اجرا میکنند از این رو آنها را نیز در فایل جداگانهای مینویسید.
از آنجا که فایلهای مختلفی دارید که با نوع داده String کار میکنند، باید یک دایرکتوری به نام string بسازید و همه فایلهای مرتبط با string را درون آن قرار دهید. در نهایت همه این دایرکتوریها را در یک دایرکتوری والد قرار دهید که پکیج شما را تشکیل میدهند. بدین ترتیب کل ساختار پکیج به صورت زیر درمیآید:
در ادامه به طور کامل روش ایمپورت کردن تابعها و متغیرها از پکیج و این که چطور همه چیز با هم درمیآمیزد تا یک پکیج تشکیل یابد توضیح خواهیم داد. اما در حال حاضر، تصور کنید پکیج شما به صورت یک دایرکتوری شامل فایلهای go. است.
هر برنامه Go باید بخشی از یک پکیج باشد. یک برنامه منفرد اجرایی Go باید اعلان package main را داشته باشد. اگر برنامه بخشی از پکیج main باشد، در این صورت go install یک فایل باینری میسازد که در زمان اجرا تابع main برنامه را فراخوانی میکند. اگر برنامه بخشی از چیزی به جز main باشد در این صورت با اجرای دستور go install یک فایل package archive ایجاد میشود. اگر از این توضیحات سردرگم شدهاید جای نگرانی نیست، چون در ادامه همه آنها را به تفصیل توضیح خواهیم داد.
در ادامه یک پکیج اجرایی ایجاد میکنیم. چنان که میدانیم برای ایجاد یک فایل اجرایی باینری برنامه ما باید بخشی از پکیج main باشد و یک تابع main داشته باشد که نقطه ورودی اجرای برنامه است.
نام یک پکیج همان نام دایرکتوری شامل آن است که در دایرکتوری src قرار دارد. در حالت فوق، app نام پکیج است زیرا app دایرکتوری فرزند دایرکتوری src است. چرا که دستور go install app در زیردایرکتوری app درون src مسیر GOPATH به دنبال فایل میگردد. سپس پکیج را کامپایل میکند و فایل اجرایی باینری app را درون دایرکتوری bin کامپایل میکند. این فایل باید از ترمینال قابل اجرا باشد، زیرا دایرکتوری bin در PATH قرار دارد.
اعلان پکیج باید نخستین خط در کد پکیج باشد و در مثال فوق به صورت package main دیده میشود و میتواند از نام پکیج متفاوت باشد. از این رو ممکن است پکیجهایی را مشاهده کنید که نام پکیج (نام دایرکتوری) از اعلان پکیج متفاوت باشد. زمانی که یک پکیج را ایمپورت میکنید، اعلان پکیج برای ساخت متغیر ارجاع پکیج استفاده میشود. در ادامه این موضوع را بیشتر توضیح خواهیم داد.
دستور <go install <package به دنبال هر فایلی میگردد که اعلان پکیج main را درون دایرکتوری package مفروض داشته باشد. اگر فایل را پیدا کند، در این صورت Go میفهمد که یک برنامه اجرایی است و باید فایل باینری آن را ایجاد کند. یک پکیج میتواند فایلهای زیادی داشته باشد، اما فقط یک فایل دارای تابع main است زیرا آن فایل نقطه ورودی اجرای برنامه خواهد بود.
اگر پکیجی شامل فایلی با اعلان پکیج main نباشد، در این صورت Go یک فایل آرشیو پکیج (با پسوند a.) درون دایرکتوری pkg میسازد.
از آنجا که app یک پکیج اجرایی نیست، یک فایل app.a درون دایرکتوری pkg ایجاد کرده است. ما نمیتوانیم این فایل را اجرا کنیم زیرا یک فایل باینری نیست.
جامعه Go پیشنهاد میکند که از نامهای ساده و سرراست برای پکیجها استفاده کنید. برای نمونه strutils برای تابعهای string utility و یا http برای تابعهای مرتبط با درخواستهای HTTP مناسب هستند. نامگذاری پکیجها به صورت under_scores ،hy-phens یا mixedCaps توصیه نمیشوند.
چنان که پیشتر توضیح دادیم، دو نوع پکیج وجود دارند. یک پکیج اجرایی و یک پکیج کاربردی (utility). پکیج اجرایی اپلیکیشن اصلی شما است چون آن را اجرا خواهید کرد. پکیج کاربردی به تنهایی نمیتواند اجرا شود، بلکه کارکردهای یک پکیج اجرایی را از طریق ارائه تابعهای کاربری و دیگر موارد مهم بهبود میبخشد.
چنان که میدانیم پکیج چیزی به جز یک دایرکتوری نیست. بنابراین در ادامه ابتدا یک دایرکتوری به نام greet درون دایرکتوری src میسازیم و چند فایل در آن ایجاد میکنیم. این بار یک اعلان package greet در ابتدای فایل مینویسیم تا بیان کنیم که این یک پکیج کاربردی است.
یک پکیج کاربردی برای ارائه برخی متغیرهای به پکیجی که آن را ایمپورت کند طراحی شده است. این وضعیت شبیه به ساختار export در جاوا اسکریپت است. Go در صورتی یک متغیر را اکسپورت میکند که نام متغیر با حروف بزرگ آغاز شده باشد. همه متغیرهای دیگر که با حروف کوچک آغاز میشوند در پکیج به صورت خصوصی تعریف شدهاند.
از اینجا به بعد در این مقاله ما قصد داریم از کلمه variable برای توصیف اکسپورت یک عضو استفاده کنیم، اما توجه داشته باشید که اکسپورت اعضا میتوانند از هر نوع مانند constant ،map ،function ،struct ،array ،slice و غیره باشند.
در ادامه یک متغیر را از فایل day.go اکسپورت میکنیم.
در برنامه فوق متغیر Morning از پکیج اکسپورت میشود، اما متغیر morning اکسپورت نمیشود زیرا با حرف کوچک آغاز شده است.
اکنون به یک پکیج اجرایی نیاز داریم که پکیج greet ما را مصرف کند. در ادامه یک دایرکتوری app درون دایرکتوری src میسازیم و فایل entry.go را با اعلان پکیج main و تابع main ایجاد میکنیم. توجه داشته باشید که در این حالت پکیجهای Go یک سیستم نامگذاری فایل مدخل مانند index.js در Node ندارند. برای هر پکیج اجرایی یک فایل با تابع main به عنوان فایل مدخل برای اجرا استفاده میشود.
برای ایمپورت کردن یک پکیج از ساختار import به همراه نام پکیج استفاده میکنیم.
برخلاف دیگر زبانهای برنامهنویسی نام یک پکیج باید مسیر زیرمجموعه مانند some-dir/greet باشد تا Go به طور خودکار مسیر را به پکیج greet بیابد. این موضوع را در بخش پکیجهای تودرتو در ادامه بیشتر توضیح خواهیم داد.
Go ابتدا در دایرکتوریهای درون دایرکتوری GOROOT/src جستجو میکند و اگر پکیج را پیدا نکند در این صورت به دنبال GOPATH/src میگردد. از آنجا که پکیج fmt بخشی از کتابخانه استاندارد Go است که در GOROOT/src قرار دارد، از آنجا ایمپورت میشود. از آنجا که Go نمیتواند پکیج greet را درون GOROOT بیابد، درون GOPATH/src را میگردد و آنجا آن را مییابد.
برنامه فوق یک خطای کامپایل صادر میکند، چون متغیر morning از پکیج greet قابل مشاهده نیست. چنان که مشاهده میکنید ما از نمادگذاری نقطهای برای دسترسی به اعضای اکسپورت شده یک پکیج استفاده کردهایم. زمانی که یک پکیج را ایمپورت میکنید، Go یک متغیر سراسری با استفاده از اعلان پکیج ایجاد میکند. در حالت فوق، greet یک متغیر سراسری است که از سوی Go ایجاد شده است، زیرا ما از اعلان package greet در برنامههای که در پکیج greet گنجانده شدهاند استفاده کردهایم.
ما میتوانیم پکیجهای fmt و greet را با هم با استفاده از ساختار گروهبندی (پرانتزها) ایمپورت کنیم. این بار برنامه ما به درستی کامپایل میشود زیرا متغیر Morning از خارج پکیج قابل دسترسی است.
ما میتوانیم یک پکیج را درون پکیج دیگر به صوت تودرتو تعریف کنیم. از آنجا که در Go هر پکیج صرفاً یک دایرکتوری است این کار مانند ایجاد یک دایرکتوری فرعی درون یک پکیج از قبل موجود است. تنها کاری که باید انجام دهیم ارائه یک مسیر نسبی از پکیجهای تو در تو است.
چنان که پیشتر توضیح دادیم، دستور go run یک برنامه را کامپایل کرده و اجرا میکند. ما میدانیم که دستور go install پکیجها را کامپایل میکند و فایلهای اجرایی باینری یا فایلهای آرشیو پکیج را میسازد. این کار به منظور اجتناب از کامپایل تکراری پکیجها اجرا میشود. دستور go install یک پکیج را از قبل کامپایل میکند و Go به فایلهای a. اشاره میکند.
به طور کلی زمانی که یک پکیج شخص ثالث را نصب میکنید Go پکیج را کامپایل میکند و فایل آرشیو پکیج را میسازد. اگر پکیج را به صوت محلی نوشته باشید، در این صورت IDE ممکن است آرشیو پکیج را به محض این که فایل را در پکیج ذخیره کردید یا هنگامی که پکیج تغییر یافت ذخیره کند. در صورتی که افزونه Go را روی VSCode نصب کرده باشید، پکیج را زمانی کامپایل میکند که آن را ذخیره کنید.
زمانی که یک برنامه Go را اجرا میکنید، کامپایلر Go از ترتیب اجرایی خاصی برای پکیجها، فایلها در پکیج و اعلان متغیر در پکیج پیروی میکند.
دامنه به منطقه خاصی از بلوک کد گفته میشود که یک متغیر تعریف شده در آن قابل دسترسی است. دامنه یک پکیج منطقهای درون آن پکیج است که یک متغیر از درون پکیج قابل دسترسی باشد. این منطقه بالاترین سطح بلوک هر فایلی در پکیج است.
نگاهی به دستور go run بیندازید. این بار به جای اجرای یک فایل، یک الگوی سراسری داریم که شامل همه فایلهای درون پکیج app میشود که باید اجرا شوند. Go به قدر کافی هوشمند است تا یک نقطه ورودی برای اپلیکیشن به صورت entry.go تشخیص دهد، زیرا تابع main را دارد. ما میتوانیم از یک دستور مانند زیر نیز استفاده کنیم (ترتیب نام فایل اهمیتی ندارد):
go run src/app/version.go src/app/entry.go
دستورهای go install یا go build به یک نام پکیج نیاز دارند که شامل همه فایلهای درون یک پکیج است و از این رو لازم نیست آنها را به صورت فوق مورد اشاره قرار دهیم.
اگر به موضوع اصلی خود بازگردیم، میتوانیم از متغیر version که در فایل version.go اعلان شده است در هر جای پکیج استفاده کنیم گرچه اکسپورت نشده است، زیرا در دامنه پکیج اعلان شده است. اگر متغیر باشد در یک تابع اعلان شده باشد، در دامنه پکیج نخواهد بود و برنامه فوق در زمان کامپایل با خطا مواجه میشود.
شما مجاز به اعلان مجدد متغیر سراسری با همان نام در همان پکیج نیستید. از این رو زمانی که متغیر version اعلان میشود دیگر نمیتوان آن را در دامنه پکیج مجدداً اعلان کرد. اما میتوان آن را در جای دیگر مجدداً اعلان کرد.
زمانی که یک متغیر مانند a به متغیر دیگری مانند b وابسته باشد، متغیر b باید پیش از آن تعریف شده باشد، در غیر این صورت برنامه کامپایل نمیشود. Go از این قاعده درون تابعها استفاده میکند.
اما زمانی که این متغیرها در دامنه پکیج استفاده شوند، میتوان آنها را در چرخههای مقداردهی اولیه اعلان کرد. به مثال ساده زیر توجه کنید.
در مثال فوق، ابتدا c اعلان شده است، زیرا مقدار آن قبلاً اعلان شده است. در چرخه بعدی مقداردهی b اعلان میشود، زیرا به c وابسته است و مقدار c قبلاً اعلان شده است. در چرخه نهایی مقداردهی a اعلان شده است و مقدار b به آن انتساب یافته است. Go میتواند چرخههای مقداردهی پیچیدهای مانند وضعیت زیر را مدیریت کند.
در مثال فوق، ابتدا c اعلان شده است و سپس b اعلان میشود، زیرا مقدار آن وابسته به c است و در نهایت a اعلان میشود، چون مقدار آن به b بستگی دارد. شما باید از هر حلقه مقداردهی مانند زیر که مقداردهی اولیه وارد حلقه بازگشتی میشود اجتناب کنید:
مثال دیگری از دامنه پکیج مانند زیر حالتی است که تابع f در یک فایل جداگانهای باشد که به متغیر c از فایل اصلی ارجاع داده باشد.
تابع init نیز مانند تابع main از سوی Go زمانی که پکیج مقداردهی اولیه میشود فراخوانی خواهد شد. این تابع هیچ آرگومانی نمیگیرد و هیچ مقداری بازنمیگرداند. تابع init به صورت صریح از سوی Go اعلان شده است، چون نمیتوانید از جای دیگری به آن ارجاع دهید و یا آن را به صورت ()init فراخوانی کنید. شما میتوانید چندین تابع init در یک فایل یا پکیج داشته باشید. ترتیب اجرای تابع init در یک فایل بر اساس ترتیب نمایش آنها خواهد بود.
شما میتوانید تابع init را در هر جای پکیج خود داشته باشید. این تابعهای init به ترتیب الفبایی فراخوانی میشوند.
پس از آن که همه تابعهای init اجرا شدند، تابع main فراخوانی میشود. زیرا وظیفه اصلی تابع init مقداردهی اولیه متغیرهای سراسری است که در چارچوب سراسری قابل مقداردهی نیستند. برای نمونه یک آرایه را مقداردهی اولیه میکند.
از آنجا که ساختار for در دامنه پکیج Go معتبر نیست، میتوانیم آرایه integers با اندازه 10 را با استفاده از حلقه for درون تابع init مقداردهی اولیه کنیم.
زمانی که یک پکیج را ایمپورت میکنید، Go یک متغیر با استفاده از اعلانهای پکیج ایجاد میکنید. اگر چندین پکیج را با نام یکسان ایمپورت کنید، منجر به بروز تداخل میشود.
از این رو از «اسامی مستعار پکیج» (package alias) استفاده میکنیم. بین کلیدواژه inport و نام پکیج یک متغیر میآوریم که آن را به یک متغیر جدید برای ارجاع به پکیج تبدیل میکند.
در مثال فوق، پکیج greet/greet از سوی متغیر child مورد ارجاع قرار میگیرد. شاید متوجه شده باشید که ما پکیج greet را با کاراکتر زیرخط به صورت یک نام مستعار اعلان کردیم. زیرخط، یک کاراکتر خاص در Go است که به عنوان کانتینر null عمل میکند. از آنجا که ما در حال ایمپورت کردن پکیج greet هستیم، اما از آن استفاده نمیکنیم، کامپایلر Go از این موضوع شکایت میکند. برای اجتناب از این وضعیت، ما یک ارجاع به آن پکیج با استفاده از _ حفظ میکنیم و بدین ترتیب کامپایلر Go آن را نادیده میگیرد.
تعریف نام مستعار برای یک پکیج با یک زیرخط که به ظاهر هیچ کاری انجام نمیدهد، در برخی موارد زمانی که میخواهید یک پکیج را مقداردهی اولیه کنید؛ اما از آن استفاده نکنید، کاملاً مفید خواهد بود.
تا به اینجا، همه مسائلی که مرتبط با پکیجها بود را توضیح دادیم. اینک نوبت آن رسیده است که درک خود در مورد شیوه مقداردهی اولیه برنامهها در Go را جمعبندی کنیم.
در ادامه مثالی کوچک را ملاحظه میکنید:
نصب پکیجهای شخص ثالث چیزی به جز کلون کردن کد ریموت درون یک دایرکتوری محلی به صورت <src/<package نیست. متأسفانه Go از نسخهبندی پکیجها پشتیبانی نمیکند و روشی نیز برای مدیریت پکیجها ارائه نکرده است.
از آنجا که Go یک رجیستری مرکزی رسمی برای پکیجها ندارد از شما میخواهد که نام میزبان و مسیر پکیج را مورد اشاره قرار دهید.
go get -u github.com/jinzhu/gorm
دستور فوق فایلهایی را از URL به نام http://github.com/jinzhu/gorm ایمپورت میکند و آنها را در دایرکتوری ذخیره میکند. چنان که در بخش پکیجهای تودرتو بررسی کردیم، میتوان پکیج gorm را مانند زیر ایمپورت کرد:
package main import "github.com/jinzhu/gorm" // use ==> gorm.SomeExportedMember
بنابراین اگر یک پکیج ساختید و خواستید افراد از آن استفاده کنند کافی است آن را روی گیتهاب منتشر کنید. اگر پکیج شما اجرایی است، افراد میتوانند از آن به عنوان یک ابزار خط فرمان استفاده کنند، در غیر این صورت باید آن را در برنامه خود ایمپورت کنند و از آن به عنوان یک ماژول کاربردی استفاده کنند. در این حالت تنها کاری که باید انجام دهید اجرای دستور زیر است:
go get github.com/your-username/repo-name