Node Master
1.02K subscribers
24 photos
2 files
156 links
Group Chat: @nodemastergp
Admin: @napoleon_n1
Download Telegram
بزارید با یک خلاصه از Deno شروع کنیم.
کلا Deno هم مثل Node یک Runtime Environment هست که شما کد جاوا اسکریپت مینویسی و با استفاده از API هایی که Deno در دسترس شما قرار میده میتوانید با سیستم عامل ارتباط بگیرید.
مثل ایجاد HTTP Server یا TCP Socket یا خواندن و نوشتن فایل.

از ویژگی های مثبت Deno بخوام بگم موضوعات زیر هست.
- سرعت بالاتری که نسبت به Node داره.
- با استفاده از permission سیستمی که داره امنیت خیلی زیادی به پروژه های شما میده که Node در نسخه 20 LTS سعی کرد این ویژگی رو که از Deno الهام گرفته اجرا کنه
مثال :
شما یک فایل myfile.txt دارید که یک متن داخلش نوشته شده و میخواید از روی دیسک بخونید.
// main.ts
const result = Deno.readTextFileSync("myfile.txt");
console.log(result);
با دستور زیر اگر بخواید برنامه رو run کنید
deno run main.ts
ازتون میپرسه که آیا به برنامه اجازه خوندن فایل روی دیسک با جزیات دقیق میدی یا خیر که با وارد کردن Y همه چی اوکی میشه.
Deno requests read access to "myfile.txt".
├ Requested by `Deno.readFileSync()` API.
├ Run again with --allow-read to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) > y
حالا شاید این موضوع احمقانه بنظر برسه ولی اگر یک پکیج که یک شخص ناشناس نوشته و شما از npm نصب بکنید اگر بخواد خراب کاری کنه بهتون خبر میده و متوجه میشید و قبلا این موضوع در node خیلی مشکلات امنیتی ایجاد کرده بود.
البته حالا node هم همین ویژگی رو داره اوکی میکنه.
https://t.iss.one/NodeMaster/7
- موضوع بعدی این که به راحتی #TypeScript رو اجرا میکنه و نیازی به transpile کردن به js برای اجرا نداره و خودش مستقیم ts رو اجرا میکنه. مثل ts-node.
- نکته بعدی این که سعی بر این شده که از Web api های استاندارد برای interface شدن با api های سیستم عامل استفاده بشه که این میتونه مزیت خوبی باشه به این دلیل که اگر web api های js رو بلد باشین اینجا نیازی نیست چیز جدیدی یاد بگیرید.
- موضوع بعدی این که از پکیج های npm هم میتوانید داخل deno استفاده کنید. پکیج های npm که برای node توسعه داده شدند سرعت کمتری نسبت به پکیج های native خود deno دارند و این که مشکلاتی هم از نظر سازاگاری هست. درکل در بهترین حالت این هست که برنامتون کامل از api های deno استفاده کنه.
- به راحتی میتونید از برنامتون خروجی Single executable بگیرید با دستور و روی هر پلتفرمی اجرا کنید.
deno compile main.ts
به عنوان مثال یک http web server در deno
```json

{
"name": "Node Master",
"author": "imanhpr"
}
```

```javascript
import user from "./user.json" with {type : "json"};

Deno.serve({port : 3005} , (req) => {
return new Response(JSON.stringify(user))
})

```
به این تکه کد شما یک http server برای پروداکشن دارید با پرفرومنس بالا.

از نکات منفی Deno بخوام بگم
- کامینیوتی کوچک
- پکیج های 3rd party کم
- کمبود منابع آموزشی.
در کل Deno برای با تجربه تر ها خوبه و برای تازه کار خوب نیست بازار کارش مشخص نیست و صرفا به عنوان سرگرمی باید بهش نگاه کرد هرچند پتانسیل خوبی داره و اینجا لیست کوچیکی از ویژگی هاش رو گفتم.
باهاش چنتا برنامه سعی میکنیم بنویسم و یکم نگاهی بهش بندازیم.
👍8
رشته پست مربوطه :‌ https://t.iss.one/NodeMaster/81

خب بریم سراغ باگی که قرار بود راجع بهش صحبت کنیم. بزارید یکم راجع تاریخچه باحال این باگ صحبت کنیم.
Null References: The Billion Dollar Mistake
اولین بار این null رو در زبان ALGOL در سال 1965 درست کرد فقط به دلیل این که پیاده سازیش خیلی راحت بود براش. کلا درمورد null ها همیشه همه جا بحث زیاده و به این دلیل بهش میگن billion doller mistake که یهویی داخل runtime یچیزی میبینی که نباید و برنامه کرش میکنه. مقالات زیادی راجع بهش هست که میتونید مطالعه عمیق تری داشته باشید راجع بهش و یک تایم دیگه خود این موضوع رو عمیق تر صحبت میکنیم.
توضیح باگ‌: شما اگر قبل از set کردن مقدار age با استفاده از setter متد sum2 رو invoke کنید به دلیل این که مقدار #age تعریف نشده و undefined (یا همون null‌) هست و وقتی یک عدد رو با undefined جمع کنید مقدار NaN رو میگیرید که همین خودش یک موضوع دیگس.
بزارید اول این رو بگم که یکی از دلایلی که میگن "js is garbage" همین Billion Dollar Mistake به دلیل این که نه تنها یک مقدار بلکه دو مقدار برای نشان دادن null داریم یعنی خود null و undefined و هرچی راجع به جک این موضوع صحبت کنیم واقعا کمه!
این باگ رو شما اگر از #typescript استفاده کنید در زمان compile میتونید جلوگیری کنید به دلیل این که ts اگر strictnullcheck در ts فعال باشه شما رو مجبور میکنه که این رو درنظر بگیرید.
class BuggyCls {
#age?: number;
set age(value) {
this.#age = value;
}

get age() {
return this.#age;
}

sum2() {
if (this.#age === undefined || this.#age === null) {
return this.#age + 2;
}
throw new TypeError("Please set a number value for age");
}
}

شما در خط دوم به ts میگید که این مقدار میتواند undifiend هم باشد و در متد sum شما رو مجبور میکنه که از guard استفاده کنید تا کدتون رو امن کنید. به این موضوع میگن null safety. یک روش دیگ هم برای تعریف فیلدی که میتواند null هم باشد هست که من معمولا از اون روش استفاده میکنم.
#age: number | null = null;

هر دو روش تفاوتی در نهایت ندارن و سلیقه هست من به این دلیل از این استفاده میکنم چون خیلی explicit تر هست و معمولا میگن از undifiend مستقیم استفاده نکنید و برای جاهایی بزاریدش که خود js engine اون رو برمیگردونه.
این موضوع هم البته صرفا یک practise هست و شما باید در نهایت استایل خودتون رو داشته باشید.

مطلب بعدی راجع به NaN خیلی صحبت میکنیم این همون موضعی بود که حتی من هم وقتی داشتم سوال طرح میکردم حواسم بهش نبود. مطلب بعدی جالبه چون که به زبان های دیگ مثل پایتون نگاهی میندازیم تا درک بهتری از دلیل این موضوع داشته باشیم.

#Tip
👍9
Node Master
در پست قبلی راجع به اصل باگ توضیحاتی دادیم ولی رسیدم سروقت این که چرا ما NaN میگیریم. بزارید با یک مثال در #python و #javascript رو کنار هم بزاریم و اونجا میتونیم بهتر متوجه بشیم. >>> 2 + "text" Traceback (most recent call last): File "<stdin>", line 1,…
یکی از قابلیت های خیلی خوب #typescript که خیلی کم استفاده میشه "type predicate" هست. به این شکل هست که شما در runtime میتونید یک interface رو valid کنید. همونطور که میدونید interface ها در typescript کلا در کد transpile نمیشوند و معمولا اکثرا با استفاده از type cast میان یک object رو تبدیل میکنن به interface. بزارید مثال بزنم.
interface IUser {
name: string;
age: number;
}

function main() {
// data will come in runtime from result of an api call.
const data: any = { username: 'imanhpr' };
const castedData: IUser = data; // Warning
}

اینجا یک interface داریم که attr هاش مشخصه ولی همینطوری که میبینید castedData قرار هست یک object باشه که اون interface رو داشته باشه ولی خب در حقیقت data از یک جنس دیگس و تایپ اسکریپت اینجور مسایل رو نمیتون پیدا کنه و ما باید
بهش کمک کنیم.
نیاز به یک چیزی داریم که در runtime بتونه صدرصد به من اطمینان بده که type درستی در اون object هست. اینجاس که "type predicate" میاد به نجات ما.

function isUser(input: unknown): input is IUser

معمولا یک فانکشن هست که یک ورودی با تایپ نامشخص رو میگیره و تبدیل میکنه به یک class یا interface یا type و convention هم این هست که با is شروع میشن مثل isProduct , isAdmin و ... .
حالا چطور ما در runtime ثابت کنیم که input ما کاملا interface رو implemet کرده.
function isUser(input: unknown): input is IUser {
return (
typeof input === 'object' &&
'name' in input &&
'age' in input &&
typeof input.age === 'number' &&
typeof input.name === 'string'
);
}

با استفاده از کمک کلمات کلیدی typeof و in در #javascript. با استفاده از typeof مشخص میکنیم که تایپ یک object چی هست به عنوان مثال وقتی input تایپش unknown هست یعنی ما هیچی نمیدونیم ازش پس اول میایم میگیم اگر object بود ادامه.
بعد با کمک in چک میکنیم که اون attr ها وجود داشته باشند و بعد دوباره با typeof تایپ اون attr ها رو چک میکنیم که درست باشند.
در حقیقت اینجا به صورت خیلی imperative شکل و ظاهر interface رو دارید replicate میکنید در runtime. در حقیقت دوتاشون یک کار میکنن ولی در جاهای مختلف. یکیشون در runtime و اون یکی در compile time. این نکته در زبان ها دیگ یکم متفاوت هست یعنی
جاوا یا golang یکم متفاوت هست.

حالا اگر همه این ها رو جمع کنیم کد کامل به این شکل میشه
interface IUser {
name: string;
age: number;
}

function isUser(input: unknown): input is IUser {
return (
typeof input === 'object' &&
'name' in input &&
'age' in input &&
typeof input.age === 'number' &&
typeof input.name === 'string'
);
}

function main() {
// data will come in runtime from result of an api call.
const data: any = { username: 'imanhpr' };
if (isUser(data)) {
// logic
}
throw new TypeError('Invalid user');
}


نکته اینجا هست که شما type narrowing هم در if block میگرید و میتونید استفاده کنید و صدرصد به شما safety هم در compile time و هم در runtime میده و مجبورین اون case که اگر این interface هم نبود هندل کنید.

#Tip
👍5
امروز یک برنامه کلاسیک HelloWorld با استفاده از #webassembly module مینویسیم و import در #NodeJS صحبت میکنیم برای خودم خیلی باحال بود و تصور این که چه قدرتی این کار داره واقعا جذابه.
این که #NodeJS کند هست همه قبول داریم ولی خب اونقدری کند نیست که بگیم بد هست. حتی روی scale بزرگ هم جواب میده و مشکلی نیست ولی گاهی شاید بخواهیم قدرتش رو با ابزار های کمکی بیشتر کنیم. یکی از این راها استفاده از C++ N-API هست که میشه native module هایی که با CPP توسعه داده شدن رو استفاده کنیم و اصن کلا کل nodejs همین هست. تازگی ها api اومده که میتونیم کد های #rust هم bind کنیم که اسمش رو یادم نمیاد. معمولا این کارا رو زمانی انجام میدن که یک پروژه بزرگ روی nodejs هست و هزینه بازنویسی روی زبان های سریعتر مثل golang خیلی زیاد هست و ارزش نداره و با استفاده از این تکنیک ها قدرتش رو بیشتر میکنن. ( این که golang سریعتره دلیل بر بهتر بودن لزوما نیست ) از این موضوع بگذریم و بریم سر وقت مثال و کد.

اگر از #ESM استفاده میکنید قبلا بهتون گفته بودم که چطور json رو مستقیم import کنید بدون استفاده از fs.readfile و ... . یکی دیگه از ویژگی های #ESM قابلیت import کردن #WASM ماژول ها هست. WASM ها ماژول های نوشته شده با webassambly هست که سرعت near native داره که مقدمه کوچیکی بالا گفتم حالا بریم یکم مثال بزنیم. وقتی راجع به سرعت near native صحبت میکنیم شوخی نیست!
کلا با استفاده از تکنولوژی های زیادی میشه خروچی wasm گرفت از cpp و rust گرفته تا #AssamblyScript که یکچیز شبیه به #TypeScript هست ولی در اینجا ما از فرمتی به اسم #WAT یا که میشه WebAssambly Text Format استفاده میکنیم برای برنامه HelloWorld. حالا بریم با مثال صحبت کنیم.
(module
(memory 1)

(export "memory" (memory 0))
(data (i32.const 0) "Hello, World!\n\00")
(func (export "getTextOffset") (result i32)
i32.const 14
return
)
)

جدا از syntax یکی از بزرگترین چالش هایی که اینجا داریم Memory management هست که در خام ترین حالت ممکن هست تقریبا و اصلا نمیشه اینجا کوچک ترین اشاره به جزیاتش کرد ولی خب خط اول (memory 1) به مقدار 64Kib یعنی 65536 حافظه خطی در اختیار شما قرار میده. (چالش اصلی همین خطی بودن حافظه و manage کردنش برای دیتا های دیگس.
حالا با استفاده از export memory خط بعد این قسمت از حافظه رو که از WebAssembly.Memory برای دسترسی در کد #nodejs میایم و export میکنیم.
بعد با استفاده از data روی خونه اول حافظه شروع میکنیم به نوشتن بایت های helloworld و مهمترین و جذاب ترین قسمتش که کنجکاوی به وجود میاره استفاده از "\00". اینجا میخوام یک سوال مطرح کنم برام توی کامنت بگید که چرا از "\00" استفاده شده ؟
بعد میایم یک فانکشن تعریف میکنیم که یک عدد i32 برمیگردونیم که آدرس اخرین خونه ای هست که hello world داخلش قرار داره یعنی از 0 شروع شده تا 14.
حالا باید این رو compile کنیم به wasm که ابزار های مختلف هست. میتونید این کد رو کپی کنید و از سایت https://webassembly.github.io/wabt/demo/wat2wasm/ استفاده کنید برای تست و خروجی wasm رو بگیرید.
حالا آمده ایم بریم برای بخش node.
import * as mywasm from "./mywasm.wasm";

const buf = Buffer.from(mywasm.memory.buffer);

const helloWorld = buf.subarray(0, mywasm.getTextOffset());

console.log(helloWorld.toString("utf-8"));

ماژول کامپایل شده رو import میکنیم و دوتا export در کد wat میبینید. یکی memory که حافظه خام ما هست که تبدیل میکنیم به buffer ( بدون تبدیل هم میشد این کارا رو کرد ولی حال نداشتم :) )
بعد این buf ما 64kib هست درصورتی که ما فقط 14 byte از این رو میخوایم. با استفاده از export دوم یعنی فانکشن getTextOffset میایم این buf رو قیچی میکنیم و 14 byte که حاوی helloworld ما هست میگیریم و بعد با encode کردن به utf8 لاگ میگریم و تمام. حالا برای ران کردن میتونید این کار کنید.
node --experimental-wasm-modules main.js


دوستان توجه کنیدکه خیلی خیلی خلاصه کردم از اتفاق هایی که میوفته تا کوتاه بشه ولی زیر به صورت تیتر وار میگم.
- اول درمورد memory این که حالا برنامه ما اینجا کوچیک بود و زیاد مهم نیست ولی این که 64kib مموری بگیری و از 14 byte فقط استفاده کنی اصلا کار درستی نیست.
- دوم کلا حواسمون باید خیلی به encode شدن باید باشه
- سوم همون سوالی که بالا گفتم بهش فکر کنید
- چهارم اون خط تبدیل کردن به buffer نیاز نیست و راه های بهتری هست که وقت نداشتم برم دنبالشون و هدف فقط اشاره به این تکنولوژی بود.
👍6
امروز درمورد Duck Typing صحبت میکنیم. این موضوع مربوط به رفتار زبان های dynamic type میباشد. البته این تکنیک در زبان هایی static type هم در حالت های خاصی استفاده میشود. برای درک این موضوع با مثال در #Python شروع میکنیم.
class Duck:
def fly(self):
print("quak quak !")

class Airplane:
def fly(self):
print("boom boom !")

#Duck Test
def fly_test(vehicle):
vehicle.fly()

flyable = [Duck() , Airplane()]
for f in flyable:
fly_test(f)

یک جمله خیلی معروف و کلیدی در این مورد وجود داره که باتوجه به اون مثال بالا رو توضیح میدیم."if it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."
به این معنی هست "اگر شبیه به اردک شنا میکنه کواک میکنه پس احتملا اردک هست."
در زبان های dynamic type مفهومی به اسم interface نداریم و اگر یک class یک method رو تغریف کرده باشه شما بدون نیاز به type میتونید اون method رو به راحتی call کنید. الان در کد بالا هیچ نشانه ای از type نیست و فانکشن fly_test بدون داشتن دانشی از این که ایا arg ورودی fly method دارد یا خیر این متد رو call میکنه. به این کار میگن duck testing. در کل بگم به هیچ عنوان fly_test براش مهم نیست vehicle چی هست فقط براش مهم هست که fly وجود داشته باشد.
حالا سوال پیش میاد که اگر این متد نبود چی؟ الان در این مثال اگر اگر متد fly نباشه روی یکی از این کلاس ها خب runtime error داریم و برنامه crash میکنه. دو دیدگاه برای جلوگیری از این مدل runtime error ها داریم که در پست بعدی برسی میکنیم هردو رو.
نکته جالب دیگه این هست که Airplane و Duck این دو کلاس هیچ رابطه ای با هم ندارند و صرفا فقط به صورت قراردادی هردو یک fly method رو implement کردن. در #TypeScript از Duck Testing زیاد استفاده میشه برای داشتن کد TypeSafe در حقیقت تکنیک Type Predict به نوعی Duck Test میتونه محسوب بشه.

این مقدمه ای برای این بحث بود. در حقیقت برای داشتن همچین مدل رفتاری مدل های فکری مختلفی در زبان های مختلف هست. در زبان #Java ما از inerface ها و abstract class ها استفاده میکنیم در #GoLang شما یک نوع دیگه از Duck Typing رو میبینید که بهش میگن Structural Typing و interface های golang بهمون کمک میکنن در #TypeScript هم Structural Typing به شدت مرسوم هست و مثال خواهیم زد، در پایتون که Duck Typing رو دیدید ولی نکته جالب اینجا هست که Structural Typing و abstract class ها هم در پایتون خیلی بهمون کمک میکنن ولی خب کمتر کسی از وجود این ها خبر داره.
این نکته هم بگم که هیچ کدوم از این راه حل ها نسبت به دیگری برتری خاصی ندارند صرفا نوع تفکره بیشتر البته البته زبان های Static و TypeSafe میتونن develop رو راحت تر کنن در این مورد. این که کدوم تکنیک در کدوم زبان استفاده میشه مهم هست چون که به عنوان مثال Duck Typing در java خیلی مرسوم نیست و استفاده ازش سخت تره اما در یک حالت های خاصی خیلی میتونه در این زبان کمک کنه.
#Tip
👍11
Node Master
امروز درمورد Duck Typing صحبت میکنیم. این موضوع مربوط به رفتار زبان های dynamic type میباشد. البته این تکنیک در زبان هایی static type هم در حالت های خاصی استفاده میشود. برای درک این موضوع با مثال در #Python شروع میکنیم. class Duck: def fly(self): …
قبلا درمورد Type Predict ها در #TypeScript صحبت کردیم از این لینک یک مطالعه کنید

https://t.iss.one/NodeMaster/90

و این که یک قسمت گفتیم که دو دیدگاه برای فیکس کردن همچین crash هایی در Runtime وجود دارد.

- Easier to ask for forgiveness than permission (EAFP)
- Look before you leap (LBYL)
قبل از این که درمورد این دو دیدگاه صحبت کنیم باید بگم که هرکدوم از این ها در یک زبان هایی مرسوم هست. به عنوان مثال در #Python نماد کد تمیز استفاده از EAFP و در #Javascript باید از LBYL استفاده کرد.
خودتون یکم برید راجع به این دوتا مطالعه کنید و بنظرتون بگید که Type Predict ها در #TypeScript از کدوم از این استایل ها استفاده میکنن؟
👍1
یک ابزار جدید از سمت مایکروسافت برای #TypeScript معرفی شده به اسم TypeSpec.
کارای زیادی میتونه کنه.
خودم چیز زیادی ازش نمیدونم ولی یک نگاهی شما هم دوست داشتید بندازید.
https://typespec.io/

#Weekly
👍8
Node Master
امروز درمورد Duck Typing صحبت میکنیم. این موضوع مربوط به رفتار زبان های dynamic type میباشد. البته این تکنیک در زبان هایی static type هم در حالت های خاصی استفاده میشود. برای درک این موضوع با مثال در #Python شروع میکنیم. class Duck: def fly(self): …
برای درک مفهوم Duck Test و کلا Duck Typing میتونیم با این مثال درمورد Promise ها نکاتی یاد بگیریم. فرض کنید فانکشنی داریم که یک Promise رو میگیره و حالا یک کاری انجام میده.
function doWork(promise) {
promise.then(() => {
console.log("Work Is Done");
});
}

const newPromise = Promise.resolve("Hello Promise");
doWork(newPromise);

مثال خیلی ساده هست. به این نکته توجه کنید که ما اینجا هیچ نوع type check نداریم. و برنامه میتونه در runtime به مشکل بخوره اگر workPromise یک instance از Promise نباشد. توجه کنید ما همین الان هم داریم از Duck Typing استفاده میکنیم چون در doWork هیچ خبری نداریم و فقط then رو call میکنیم با فرض این که Promise یا Duck باشد.
حالا کد رو یکم بهتر میکنیم.
import { types } from "node:util";

function doWork(promise) {
if (types.isPromise(promise)) {
promise.then(() => {
console.log("Work Is Done");
});
}
}

در "node:util" یک namespace داریم به اسم type که در اینجا یک سری فانکشن کمکی برای چک کردن تایپ ها ( Type predict function ) داریم که به ما کمک میکنن که کد safe تر باشه. الان این کد رو اگر اجرا کنید همه چیز اوکی هست و کار میکنه. ولی یک edge case اینجا وجود داره که خیلی کمتر به چشم میاد. با مثال بعد متوجه میشید ولی بزارید اول با یک سوال مطرح کنم موضوع رو.
اگر Promise Object ما از Native Promise خود #Javascript نباشد چه اتفاقی پیش میاد؟ قطعا این کد به مشکل میخوره چون isPromise فقط Native Promise ها رو چک میکنه و نه 3rd party ها که ممکنه در خیلی از code base ها باشه.
به عنوان مثال اگر از پکیج زیر استفاده شده باشه.
https://www.npmjs.com/package/promise
حالا اگر فقط این پکیج import شده باشه این کد به مشکل میخوره
import Promise from "promise";

حالا شما میگی ایمان خب با شرط رو با instaceof چک میکنیم که اگر این پکیج بود درست اجرا بشه. و شرط رو به این شکل عوض میکنید.
import Promise from "promise";
import { types } from "node:util";

function doWork(promise) {
if (types.isPromise(promise) || promise instanceof Promise) {
promise.then(() => {
console.log("Work Is Done");
});
}
}

دوباره اینجا یک مشکل پیش میاد چون فقط همین یک پکیج نیست. به عنوان مثال اگر bluebird استفاده کرده باشن کد مشکل میخوره. خب منظورم رو تا الان متوجه شدین بریم سراغ Duck Test. اون جمله معروف که در پست اول گفتیم رو به یاد بیارید اول و حالا دقت کنید.
درسته این پکیج ها متفاوت هستند ولی همشون interface های یکسانی دارند. اینجا دوست دارم یک رفرنس بزنم به یک جمله معروف که اگر عمیق درکش کنید چند level یک شبه در برنامه نویسی پیشرفت میکنید.
"program to interfaces, not implementations"
اینجا دقیقا این lib ها از این مفهوم استفاده کردند برای Promise به ما implementation های زیادی داریم ولی نقطه مشترک همشون داشتن interface یکسان هست که به عنوان مثال همشون متد .then دارند. اصن وقتی در مثال بالا ما بدون تغییر کد فقط با import کردن کل ساختار رو عوض کردیم که کد به مشکل خورد مثال همین موضوع هست.
حالا کافیه با استناد به این جمله کلیدی و زیبا و Duck Test و فرض گرفتن که همه این lib ها متد .then رو دارند شرط رو عوض کنیم.
function doWork(promise) {
if (promise && promise.then && typeof promise.then === "function") {
promise.then(() => {
console.log("Work Is Done");
});
}
}

حالا شما اگر شرط بالا رو به این صورت بنویسید (دقیقا مثل type predict ها در #TypeScript) میبینید که کد با هر library کار میکنه. چون شما وابستگی خودتون رو از implementation شکوندین و به interface وابسته شدین. مهم نیست که object ورودی یک instance از Promise خاصی باشد. مهم این هست که شبیه به Promise ها رفتار کند ( به عنوان مثال متد .then داشته باشد).

این بود مفهوم Duck Test به صورت کاربردی. توجه کنید که کد بالا به صورت production ready نیست و صرفا فقط برای توضیح مفاهیم بود.
امیدوارم خوشتون اومده باشه.

#Tip
#NodeJS
👍11
سوال مصاحبه درمورد #NestJS
شما وقتی با #NestJS و #TypeScript در حال توسعه #BackEnd هستید خب به صورت روتین از Injectable ها در #NestJS استفاده میکنید.
حالا با فرض این که ما یک EmailService داریم که Injectable هست و میخوایم در سرویس پایین اون رو inject کنیم و استفاده کنیم. به کد پایین دقت کنید.
@Injectable()
class MyService {
constructor(private readonly emailService : EmailService)
}

اینجا همه چی درست و عادی کار میکنه ولی اگر یکم با دقت بیشتری نگاه کنید این کد خیلی غیر عادی هست. باتوجه به این موضوع که در هنگام transpile شدن کد #TypeScript به #JavaScript تایپ ها حذف میشه.
حالا سوال اینجا هست که #NestJS چطور با استفاده از یک type یعنی EmailService متوجه میشه باید چه سرویسی رو inject کنه؟ در حقیقت اینجا از یک type یک logic داره که در برنامه ما استفاده میشه و برسی این black magic میتونه جذاب باشه.

نظراتتون رو کامنت کنید.

#Tip
👍13
اگر درحال یادگیری #NestJS هستید و یا مدت زیادی هست با این framework محبوب کار میکنید به این نکته حتما توجه کنید.
1. به هیچ عنوان از Nest CLI برای ایجاد یک پروژه جدید استفاده نکنید.
2. حداقل اگر استفاده میکنید سعی کنید dep های اضافه رو پاک کنید.

درمورد نکته اول یک اتفاق فاجعه بار درصورت رعایت نکردن میتونه رخ بده. اگر با Nest CLI پروژه ایجاد کنید و بعد به tsconfig یک نگاهی بندازید همچین خطی رو میبینید.

{ "strictNullChecks": false }

همین یک خط config توانایی این رو داره که پروژه رو تبدیل کنه به میدان مین جنگی و گوشی شما یکهویی ساعت ۱ شب زنگ میخوره کارفرما به شما میگه رو production مشکل داریم و بعد از چک کردن log ها متوجه Null pointer های زیبا خواهید شد.
قبلا درمورد این موضوع یعنی Null References: The Billion Dollar Mistake در این پست مفصل صحبت کردیم.
https://t.iss.one/NodeMaster/84

این نکته خیلی میتونه ترسناک باشه به این دلیل که اگر کسی تازه داره روی کد onboard میشه اگر معمولا فرض بر این داره که #TypeScript کمک به Null safe بودن داره و نکته جذاب اینجا هست که ترکیب false بودن این flag با همچین تصور پیشفرضی به زیبایی میتونه جهنم باشه ( به قول خارجی ها road to disaster ). چندتا config دیگ هم مربوط به safety به صورت پیش فرض در این حالت false هستن که هیچ کدوم به اندازه این ترسناک نیستن.
در ادامه اگر روی یک code base بزرگ باشید و این flag مهم از قبل false بوده بعد از true کردن این flag پروژه شما در کسری از ثانیه به رنگ خون در میاد و فرایند refactor کردن در این سناریو فرایندی بسیار هزینه بر هست. پس بهتره از همون اول پروژه این رو درنظر بگیرید.

درمورد نکته دوم این که به صورت پیش فرض #NestJS یک سری Dep به پروژه از این روش اضافه میکنه که ممکنه اصلا نیازی بهش نداشته باشید و خب این یک bad practise محسوب میشه ( YAGNI Principle ). این حالت زمان build time هم الکی اضاف میکنه و در پروژه بزرگ میتونه آزار دهنده باشه. موضوع بعد در این مورد این که بعضی از package هایی که وجود دارن در package.json آپدیت نیستن و خیلی سریعتر از زمان معمول ممکنه نیاز به آپدیت کردن پروژه داشته باشید. با نصب کردن dep ها به صورت دستی حداقل از این موضوع میتونید اطمینان پیدا کنید که از جدیدترین ورژن پکیج ها دارید استفاده میکنید.

#Tip #NodeJS
👍11
Node Master
اگر درحال یادگیری #NestJS هستید و یا مدت زیادی هست با این framework محبوب کار میکنید به این نکته حتما توجه کنید. 1. به هیچ عنوان از Nest CLI برای ایجاد یک پروژه جدید استفاده نکنید. 2. حداقل اگر استفاده میکنید سعی کنید dep های اضافه رو پاک کنید. درمورد نکته…
ترسناک تر از Dynamic Type بودن #JavaScript در حقیقت پروژه #TypeScript هست که به تو توهم Type safe بودن بده.
هروقت پروژه #TypeScript دیدین اول بیاین این قسمت رو چک کنید که مثل عکس بالا strict باشه و هیچ کدوم از flag های زیرش false نباشه.
اگر در موقع کار با #TypeScript مجبور شدین اینجا فلگی رو false کنید این برداشت رو میتونید کنید که #TypeScript بلد نیستید!
👍19
هم اکنون با این ابزار جدید میتونید #TypeScript رو به #Lua تبدیل کنید.
اگر براتون سوال هست که کجا میتونه کاربرد داشته باشه این میتونه باشه که با #Lua میتونید به عنوان مثال برای #Redis اسکریپت بنویسید و حالا میتونید این کار رو بدون یاد گرفتن #Lua و با نوشتن #TypeScript و گرفتن خروجی #Lua انجام بدید. هرچند من شخصا طرفدار این موضوع نیستم ولی جالبه.

https://typescripttolua.github.io/

#NodeWeekly
👍4
به این خط ساده و کوچک دقت کنید، توانایی این رو داره هرپروژه #FrontEnd و #BackEnd که با #NodeJS توسعه داده شده رو تبدیل به جهنم کنه.
throw 1;

اگر در #TypeScript براتون سوال پیش اومده که چرا در try catch statement تایپ err نامشخص هست، دقیقا بخاطر این موضوع هست. به این دلیل که هر نوع Object رو میشه throw کرد (این خیلی ترسناکه) و چون #JavaScript مفهومی به اسم Compile Time Error نداره و با توجه به طبیعت Dynamic و البته مفسری که #JavaScript داره منطقی هست که شما وقتی try catch استفاده میکنی type برای error تا زمان runtime مشخص نباشه. با مثال پایین بیشتر به عمق مسئله پی میبرید.
try {
throw { hoo: "Pop up from stack , NodeMaster" };
} catch (err /* type => unknown */) {
console.log(err);
}

معمولا بعضیا با استفاده از type cast تایپ error رو تبدیل به Error میکنن مثل کد زیر که این به نوع خودش میتونه یک فاجعه دیگه باشه ( آخه نابغه اگر تایپ مشخص بود که Error هست #TypeScript همون اول خودش Error میزاشت و نیاز به Type Cast نبود دیگه)
try {
throw { hoo: "Pop up from stack , NodeMaster" };
} catch (err) {
console.log((err as Error).message); // WTFFFFFFFFF !!!!
}

خب حالا سناریو های بد رو دیدم چطور جلوگیری کنیم؟ در پروژه های #FontEnd تنها روش خیلی خوبی که وجود داره استفاده از instanceof هست در #NodeJS برای #BackEnd هم میشه از این روش استفاده کرد ولی یک تکنیک دیگه وجود داره که اون بیشتر برای سناریوهای خیلی خاص #NodeJS هم کاربردی هست.
try {
throw { hoo: "Pop up from stack , NodeMaster" };
} catch (err) {
if (err instanceof Error) {
console.error("it's valid error object", err);
} else {
console.error("WHAT THE F IS THIS ???", err);
}
}

برای پروژه های #NodeJS در std مربوط به Node یک فانکشن کمکی هست به اسم isNativeError که همین کار instanceof رو میکنه. حالا سوال پیش میاد که چرا باید از این استفاده کنیم؟ در حالت هایی به عنوان مثال استفاده از "node:vm" و ایجاد یک context جدید میتونه دردسر ساز بشه و instanceof نتونه تشخیص بده Error رو که نتایجش هم مشخصه. نکات دیگه ای هم وجود داره که پیشنهاد میکنم داکیومنت مربوط به این بخش رو بخونید کامل با مثال ساده توضیح داده.
import { types } from "node:util";
try {
throw { hoo: "Pop up from stack , NodeMaster" };
} catch (err) {
if (types.isNativeError(err)) {
console.error("it's valid error object", err);
} else {
console.error("WHAT THE F IS THIS ???", err);
}
}

حالا یک سوال دیگ شاید براتون پیش بیاد که چرا اینقدر بدبینانه به مسئله نگاه میکنیم؟
تو دنیایی که #NPM وجود داره اعتماد به پکیج های 3RD Party گاهی اوقات میتونه گرون تموم بشه. شاید اون پکیج که استفاده میکنید این داستان رو رعایت نکرده باشه. پس بهتره ما دقت کنیم تا از runtime ارور های نصف شب جلوگیری کنیم و با آسوده بخوابیم.
👍16
یک Pattern وجود داره که خیلی درموردش صحبت نمیشه در اکوسیستم #JavaScript چه پروژه های #FrontEnd و چه #Backend که با #NodeJS و بقیه runtime ها توسعه داده شده باشن. این پترن خیلی ساده هست. استفاده نکردن ( یا حداقل استفاده ) از Anonymous function ها میباشد.

حالا سوال پیش میاد چرا از این پترن باید استفاده کنیم؟
وقتی دارید برنامه توسعه میدین و به نوعی چه توسط خودتون یا Framework که استفاده میکنید با فرایند IOC درگیر هستید مثل #React یا حتی #Express و ... اگر سایز پروژه بزرگ باشه برای پیدا کردن Bug ها به هیچ عنوان تکنیک console.log راه بهینه ای نیست. و معمولا از ابزار ها و تکنیک های مختلف در کنار هم استفاده میشن تا این فرایند راحت تر باشه. در کنار این مفاهیم لاگ کردن Error ها و stack trace مربوط بهشون خیلی مهم هست و خیلی کمک میکنه تا scope مربوط به به باگ رو کوچک تر کنیم و سریعتر بتونیم باگ رو پیدا کنیم.

با استفاده زیاد از Anonymous function ها که معمولا بیشتر در هنگام arg برای پاس دادن به عنوان callback استفاده میشن، stack trace شکل خوبی نخواهد داشت و اگر زیاد استفاده بشه حتی stack trace میتونه کاملا بی مصرف بشه و با این کار یکی از عناصر خیلی مهم برای debug کردن رو از دست میدیم. به مثال زیر توجه کنید.
function iocContainer(cb) {
cb()
}
iocContainer(() => { throw new Error("ugly stack trace") })

    at /home/imanhpr/codes/main.js:6:28
at iocContainer (/home/imanhpr/codes/main.js:2:5)
at Object.<anonymous> (/home/imanhpr/codes/main.js:6:1)

اینجا سایز پروژه کوچک هست و راحت میشه Error رو پیدا کرد چون جزیات زیادی در stack وجود نداره و کد framework هم در نظر گرفته نشده. اما توجه داشته باشید که اون خط آخر یعنی Object.anonymous در شرایط فشار باگ روی production خیلی میتونه ترسناک باشه.یک نکته که حتی میتونه وحشتناک تر کنه داستان رو پروژه #TypeScript بدون source map و اگر به این ترکیب esbuild هم اضافه بشه جای صحبتی اصلا باقی نمیمونه.

حالا با یک مثال واقعی تر و خیلی ساده این موضوع رو برسی میکنیم
const express = require("express")

const app = express()

app.get("/", (req, res) => {
throw new Error("ugly error")
})

app.listen(3000)

Error: ugly error
at /home/imanhpr/codes/main.js:6:11
at Layer.handle [as handle_request] (/home/imanhpr/codes/node_modules/express/lib/router/layer.js:95:5)
at next (/home/imanhpr/codes/node_modules/express/lib/router/route.js:149:13)
at Route.dispatch (/home/imanhpr/codes/node_modules/express/lib/router/route.js:119:3)
at Layer.handle [as handle_request] (/home/imanhpr/codes/node_modules/express/lib/router/layer.js:95:5)
at /home/imanhpr/codes/node_modules/express/lib/router/index.js:284:15
at Function.process_params (/home/imanhpr/codes/node_modules/express/lib/router/index.js:346:12)
at next (/home/imanhpr/codes/node_modules/express/lib/router/index.js:280:10)
at expressInit (/home/imanhpr/codes/node_modules/express/lib/middleware/init.js:40:5)
at Layer.handle [as handle_request] (/home/imanhpr/codes/node_modules/express/lib/router/layer.js:95:5)

این مثال کیلومتر ها با پیچدگی پروژه های production فاصله داره اما همین کد ساده نشون میده چقدر توجه به این نکته میتونه ساعت ها از وقت شما نجات بده و کار کم استرس
تری هم تجربه کنید. مشکل دقیقا خط اول stack trace هست که هیچی ازش وجود نداره ( در این مثال خوش شانسیم که خطی که این اتفاق افتاده دقیقا اشاره داره به همون callback ما ولی همیشه اینطور نیست )

برای داشتن زندگی شیرین تر کافیه کمتر از Anonymous function ها چه به شکل arrow و چه با function keyword استفاده کنید. و برای function ها نام مناسب انتخاب کنید. مثل سناریو پایین که rootHandler رو داریم.

app.get("/", function rootHandler(req, res) {
throw new Error("ugly error")
})
// OR
const rootHandler = (req, res) => {
throw new Error("ugly error")

}
app.get("/", rootHandler)

Error: ugly error
at rootHandler (/home/imanhpr/codes/main.js:6:11)
at Layer.handle [as handle_request] (/home/imanhpr/codes/node_modules/express/lib/router/layer.js:95:5)

حالا چند نکته باید در نظر بگیرید.
- تفاوت arrow function و function keyword رو بدونید گاهی ممکنه دردسر ساز بشه.
- اگر از #TypeScript استفاده میکنید از اهمیت source map هرگز غافل نشید.

#Tip
👍15
نسخه 20.16 LTS مربوط به #NodeJS خیلی وقته اومده و خب چون مدتی فعال نبودیم. از دست ما در رفته نگاهی بهش بندازیم.
یک فانکشن کمکی به این نسخه اضافه شده و از این زاویه جالب هست که یک حرکت eco system رو نشون میده که برای ما developer ها در در طولانی مدت میتونه خوب باشه. این که در آینده کدهای بیشتری ببینیم که به runtime خاصی وابسته نباشن و فریم ورک های جدید تر روی هر runtime به صورت native اجرا بشه.

فانکشن کمکی جدید process.getBuiltinModule
این فانکشن به ما کمک میکنه که در runtime هر ماژولی رو از std مربوط به #NodeJS به راحتی import کنیم. حالا سوال پیش میاد که چرا از خود ES import استفاده نمیکنیم. به این دلیل که اگر یک کد داشته باشیم که به #Deno وابستگی داره حالا اگر بخواهیم اون کد رو با #NodeJS ران کنیم بره و از std مربوط به Node استفاده کنه و نه Deno. بزارید با مثال این رو ببینیم. نمونه ای خوب و ساده از Factory pattern و Polymorphism هم میشه دید. اگر تازه کارتر هستید و در حال یادگیری به این مثال بیشتر دقت کنید خیلی بهتون کمک خواهد کرد.
class DenoReader {
constructor() {
this.decoder = new TextDecoder();
}
readFile(fileName) {
const byteArray = Deno.readFileSync(fileName);
const result = this.decoder.decode(byteArray);
return result;
}
}
class NodeReader {
constructor() {
this.fs = globalThis.process.getBuiltinModule("fs");
}
readFile(fileName) {
const result = this.fs.readFileSync(fileName).toString("utf-8");
return result;
}
}

function getReader() {
switch (true) {
case globalThis.Deno !== undefined:
return new DenoReader();
case globalThis.process.getBuiltinModule !== undefined:
return new NodeReader();
default:
throw new Error("No Reader available");
}
}

const nodeReader = getReader();
const nodeResult = nodeReader.readFile("my-text.txt");
console.log(nodeResult);

هدف خوندن یک فایل از روی disk هست و البته که این کد برای runtime های #Deno و #NodeJS از Native API مربوط به هر runtime استفاده کنه. باتوجه به این که این runtime ها هرکدوم دنیای خودشون رو دارن و API های خاص خودشون. اول باید یک API مشترک برای این دوتا بسازیم که بتونیم به هدفمون برسیم. اینجا Factory pattern به کمک ما میاد که بهمون اجازه میده با استفاده از type switch تصمیم بگیریم که روی کدام runtime هستیم و با توجه به اون runtime کلاس reader مربوط بهش رو بهمون میده و البته با استفاده از Duck typing در #JavaScript یا با استفاده از interface ها در #TypeScript یک interface یکسان برای خواندن فایل با دو implemetion متفاوت باید ایجاد کنیم که در بالا معادل NodeReader و DenoReader هست که readFile میشه interface یکسان بین این دو.
نکته قابل توجه این که چون اینجا مشخص نیست کدوم runtime رو قراره استفاده کنیم، استفاده از top-level import به صورت کلی کنسل هست به این دلیل که اگر کد زیر بزاریم و برنامه با Deno ران کنیم کلا هدفمون میره تو دیوار.
import fs from "node:fs";

یک سوال دیگ که پیش میاد دقیقا همین ویژگی رو ما میتونیم با استفاده از dynamic import داشته باشیم چرا از اون استفاده نکنیم؟ در جواب این موضوع مشکل خیلی خاصی به وجود نمیاد. مزیت این روش نسبت به dynamic import این هست که این روش به صورت sync فرایند import رو انجام میده درصورتی که حاصل dynamic imprort یک Promise هست که اگر ESM باشید با top-level await میتونید تمیز حلش کنید. اما اگر روی CJS باشید کثیف کاری خواهید داشت در نتیجه در این سناریو این روش جدید منطقی تر به نظر میرسه.

قبلا هم درمورد Duck typing مفصل صحبت کرده بودیم که اینجا میتونید ببینید اگر دوست داشتید.
https://t.iss.one/NodeMaster/136
👍11