Sequence ב-JavaScript

שייך לקטגוריות JavaScript

מהן פונקציות אסינכרוניות?

פונקציה אסינכרונית היא פונקציה שסיום פעולתה תלוי בפעולה של גוף אחר. התוכנית בה היא רצה אינה מחכה שאותו גוף יחזיר תשובה אלא אותו גוף "דוחף" תשובה כשהוא סיים. הדחיפה הזו מתבצעת לתוך פונקצית callback – פונקציה שהוגדרה במיוחד למטרת קבלת התשובה, ואותו גוף קורא לה עם ארגומנטים שהוא מזין. חתימתה של פונקצית ה-callback מכילה את הארגומנטים האלה, ומשתמשת בהם לצורך סיום הפעולה.

לדוגמה – שימוש ב-XMLHTTP במצב אסינכרוני. בשליחת הבקשה לשרת שאר הסקריפטים ממשיכים לרוץ והעמוד ממשיך לתפקד. לאותו אובייקט XMLHTTP מוצמד אירוע בשם onreadystatechange שרץ בכל פעם שהשרת מחזיר תשובה. זאת להבדיל ממצב סינכרוני, בו הדפדפן מחכה לתשובה מהשרת. החלון 'קופא' ורק בקבלת התשובה הכל חוזר לחיים. התנהגות זו היא פשוט לא אינטרנט-קורקט, ולא ידידותית למשתמש.

בד"כ כשתהיה לנו פונקציה אסינכרונית, נרצה שבסיומה תתבצע פונקציה אחרת. את הקריאה לפונקציה השניה נשים ב-callback של הראשונה.
אם יש לנו מספר רב של פונקציות ו-callbacks הן מתחילות להיות תלויות אחת בשניה – בסיום של A קרא ל-B, בסיום של B קרא ל-C וכו'. אם אני מוציא את B מהשרשרת – הרסתי את התור, כי הקריאה ל-C לא תתבצע.

תרחיש פונקציות אסינכרוניות בתור

ניקח לדוגמה את הרצף הבא:

  1. הרצת אפקט המשנה שקיפות של אלמנט לידי תצוגה, ובסיום:
  2. הופעה של כפתור בתוך אותו אלמנט, ובלחיצה:
  3. שליחת בקשת XMLHTTP לשרת, ובקבלת תשובה:
  4. הודעת alert של סיום

החלוקה לפונקציות די פשוטה:

function EffectElement() {
	var div=document.getElementById("div");
	var opacity=0;
	var iv=setInterval(function () {
		opacity+=.05;
		div.SetOpacity(opacity);
		div.style.filter="alpha(opacity="+(opacity*100)+")";
		if (opacity>=1) {
			clearTimeout(iv);
			ShowButton();
		}
	},50);
}
function ShowButton() {
	var button=document.getElementById("button");
	button.style.display="";
	button.onclick=function () {
		RequestServer();
	};
}
function RequestServer() {
	var xh=new XMLHttpRequest();
	xh.open("GET","url.htm",true); // true - async call
	xh.onreadystatechange=function () {
		if (xh.readyState==4) {
			div.innerHTML=xh.responseText;
			Finish();
		}
	};
	xh.send(null);
}
function Finish() {
	alert("Finish");
}
onload=EffectElement;

אנו שמים לב שב-callback של כל פונקציה בתור אפשר להפעיל את הפונקציה הבאה. אך מה יקרה במצב בו נרצה להוריד את אחת הפונקציות מהתור? נצטרך לשנות בפונקציה שהייתה אחריה את הקריאה, לפונקציה אחרת. ומה יקרה אם נרצה לשנות את הסדר? בלאגן.

פתרון גנרי לפונקציות אסינכרוניות בתור

ניסיתי לחשוב על פתרון גנרי לבעיה. כזה שיתן לי במינימום קוד ומקסימום גמישות להחליט מי בא לפני מי ואיך לקרוא לפונקציה הבאה בלי להיות תלוי בה.

הסינטקס הבסיסי הוא כזה:

var sequence=new Devign.Sequence();

sequence.Add(EffectElement);
sequence.Add(ShowButton);
sequence.Add(RequestServer);
sequence.Add(Finish);

sequence.Start();

ובתוך כל פונקצית callback יש לקדם את ה-sequence הנוכחי בעזרת

currSequence.Next();

כאשר currSequence נשלח עם הפונקציה כארגומנט יחיד.

פשוט, לא?
אפשר לשחק עם הסדר של הפונקציות, להוריד ולהוסיף כאוות נפשנו.

המחלקה Devign.Sequence

// Devign.Sequence class
Devign.Sequence=function () {
	// private fields
	// a list of functions
	this.list=[];
	this.index=-1;
	this.aborted=false;
	// /private fields

	// public fields
	this.Finished=false;
	// /public fields
};

// public methods
Devign.Sequence.prototype={
	// adds a new function
	Add:function (sequenceFunction) {
		this.list.push(sequenceFunction);
	},
	// starts the sequence from the first function
	// fires 'OnStart' if exists
	Start:function () {
		this.index=-1;
		this.aborted=false;
		this.Next();
		if (typeof(this.OnStart)=="function") this.OnStart();
	},
	// ends the sequence
	// fires 'OnEnd' if exists
	End:function () {
		if (typeof(this.OnEnd)=="function") this.OnEnd();
		this.Finished=true;
	},
	// proceeds the sequence
	// if the sequence has finished calls 'End'
	Next:function () {
		// if sequence was aborted - ignore next statements
		if (this.aborted) return;

		this.index++;

		if (this.index==this.list.length) return this.End();

		var tf=this.list[this.index];

		// calls the function with the sequence as an argument
		if (tf) tf(this);
	},
	// aborts the sequence by setting the index to 'not started' and the flag aborted to true
	Abort:function () {
		this.index=-1;
		this.aborted=true;
	}
};
// /public methods
// /Devign.Sequence class

למופע של המחלקה יש מערך של פונקציות, אליו ניתן להוסיף בעזרת המתודה Add. בנוסף קיים גם שדה מספרי בשם index השומר את האינדקס הנוכחי של הפונקציה הרצה (כאשר 1- מסמל שעוד לא התחלנו לרוץ).
למופע ניתן להגדיר גם שני אירועים – OnStart ו-OnEnd. אם הוגדרו הם יורצו בזמן המתאים.
בכל פעם שמתקדמים בתהליך, האינדקס עולה, והפונקציה נשלפת מתוך המערך. הפונקציה נקראת עם הפרמטר this שמצביע באותו זמן על מופע המחלקה.  הרפרנס למחלקה נשלח כדי שיהיה אפשר לזהותו מתוך הפונקציה, ולקדם אותו בעזרת המתודה Next.

קוד התוכנית במלואו

<script type="text/javascript" src="Devign.Sequence.js"></script>
<script type="text/javascript">
//<![CDATA[
var sequence=new Devign.Sequence();

sequence.Add(EffectElement);
sequence.Add(ShowButton);
sequence.Add(RequestServer);
sequence.Add(Finish);

onload=function () {
	sequence.Start();
};

function EffectElement(currSequence) {
	var div=document.getElementById("div");

	var opacity=0;
	var iv=setInterval(function () {
		opacity+=.05;
		div.style.opacity=opacity;
		div.style.filter="alpha(opacity="+(opacity*100)+")";
		if (opacity>=1) {
			clearTimeout(iv);
			currSequence.Next();
		}
	},50);
}
function ShowButton(currSequence) {
	var button=document.getElementById("button");
	button.style.display="";
	button.onclick=function () {
		currSequence.Next();
	};
}
function RequestServer(currSequence) {
	var div=document.getElementById("div");
	var xh=window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
	xh.open("GET","XMLHttpRequest.htm",true); // true – async call
	xh.onreadystatechange=function () {
		if (xh.readyState==4) {
			div.innerHTML=xh.responseText;
			currSequence.Next();
		}
	};
	xh.send(null);
}
function Finish(currSequence) {
	alert("Finish");
	currSequence.Next();
}
//]]>
</script>
<div id="div" style="filter:alpha(opacity=0);opacity:0;height:100px;width:100px;border:1px solid #000;background-color:#93B9CE;">
	<input type="button" id="button" value="Click" style="display:none"/>
</div>

להורדת הדגמה של Devign.Sequence.



קטגוריות

חיפוש

עיקבו אחרי (אקספרימנטלי!)

4 תגובות

כתיבת תגובה

31.12.06 בשעה 17:08
ניר טייב
(אתר)

שימושי ביותר :-) .

אני משתמש ב-Mootools.net יש שם תמיכה מובנית בזה (בעזרת מנגנון ה-Chain).

1
08.03.07 בשעה 16:41
אלישע

נראה מאוד שימושי, העניין של הסדר הקבוע מראש באמת יכול לגרום להמון עצבים.
תודה :)

2
08.09.07 בשעה 23:37
ישראל

בניגוד לשאר המגיבים, זה לא נראה לי שימושי, אבל זה נראה פתרון מעולה במקרה הצורך.
זה לא נראה לי שימושי, פשוט כי אני לא רואה סיבה להשתמש בזה כ"כ הרבה, לי לפחות לא זכור שנתקלתי בכ"כ הרבה מקרים שנצרכתי "לשרשר" פעולות.

אגב, חבל שהתחביר לא כתוב בצורה נקיה, אתה מתקמצן כל מרווח בין שורות קוד שונות (שורת רווח אחת בין פונקציה אחת לרעותה לא תוסיף לך יותר מדי אקסה בתים לנפח הקובץ אבל תעשה את הקוד בהיר יותר לעיניים), תו רווח מצידי אופרטור כגון: var a = 3 ועוד.

בהצלחה :)

3
09.09.07 בשעה 7:27
אלעד
ישראל אמר/ה:
בניגוד לשאר המגיבים, זה לא נראה לי שימושי, אבל זה נראה פתרון מעולה במקרה הצורך.
זה לא נראה לי שימושי, פשוט כי אני לא רואה סיבה להשתמש בזה כ"כ הרבה, לי לפחות לא זכור שנתקלתי בכ"כ הרבה מקרים שנצרכתי "לשרשר" פעולות.

זה שימושי מאוד באפליקציות וגם אתרים עם בקשות ajax ואפקטים שצריכים להתבצע אחד אחרי השני. יצא לי להשתמש בזה מספר רב של פעמים :)

ישראל אמר/ה:
אגב, חבל שהתחביר לא כתוב בצורה נקיה, אתה מתקמצן כל מרווח בין שורות קוד שונות (שורת רווח אחת בין פונקציה אחת לרעותה לא תוסיף לך יותר מדי אקסה בתים לנפח הקובץ אבל תעשה את הקוד בהיר יותר לעיניים), תו רווח מצידי אופרטור כגון: var a = 3 ועוד.

בהחלט עניין של הרגל ומינימליזם… לדעתי רווחים מיותרים, במיוחד בסימן =, נקודותיים, פסיקים וכו' גורמים לניפוח מיותר. מין הסתם כל אחד יכול לערוך את הקוד שלו איך שבא לו.
הבעיה נמצאת כשאתה חי בארגון גדול שבו יש הרגלי כתיבה מוכתבים ואתה רגיל לכתוב אחרת. מיקרוסופט עשו בשכל ונתנו ל-visual studio אפשרות לפרמט את הקוד (#HTML, VB, C) לפי העדפות אישיות, כך שבכל פעם שאתה מסתכל או מקבל פרוייקט של מישהו אחר, אתה יכול לפרמט אותו לפי ההגדרות שלך (ריווחים, מיקומי {}, שורות רווח וכו') בלחיצת כפתור.

4

כתיבת תגובה

תגיות מותרות לשימוש בתוכן
XHTML: אפשר להשתמש בתגים הללו: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre>