คุณเก็บรหัสด้วยการอ่าน / การเรียกกลับต่อเนื่องได้อย่างไร?


11

สรุป: มีรูปแบบการปฏิบัติที่ดีที่สุดบางอย่างที่ฉันสามารถติดตามเพื่อให้โค้ดของฉันสามารถอ่านได้แม้จะใช้รหัสอะซิงโครนัสและการโทรกลับหรือไม่


ฉันใช้ไลบรารี JavaScript ที่ทำสิ่งต่างๆมากมายแบบอะซิงโครนัสและอาศัยการโทรกลับอย่างหนัก ดูเหมือนว่าการเขียนวิธี "โหลด A, โหลด B, ... " ง่าย ๆ นั้นค่อนข้างซับซ้อนและยากที่จะติดตามโดยใช้รูปแบบนี้

ขอยกตัวอย่าง (contrived) สมมติว่าฉันต้องการโหลดรูปภาพจำนวนมาก (แบบอะซิงโครนัส) จากเว็บเซิร์ฟเวอร์ระยะไกล ใน C # / async ฉันจะเขียนสิ่งนี้:

disableStartButton();

foreach (myData in myRepository) {
    var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
    if (result.Success) {
        myData.Image = result.Data;
    } else {
        write("error loading Image " + myData.Id);
        return;
    }
}

write("success");
enableStartButton();

เลย์เอาต์รหัสเป็นไปตาม "การไหลของเหตุการณ์": อันดับแรกปุ่มเริ่มต้นถูกปิดใช้งานจากนั้นภาพจะถูกโหลด ( awaitทำให้แน่ใจว่า UI ยังคงตอบสนองได้) จากนั้นปุ่มเริ่มทำงานจะเปิดใช้งานอีกครั้ง

ใน JavaScript โดยใช้การโทรกลับฉันมาด้วยสิ่งนี้:

disableStartButton();

var count = myRepository.length;

function loadImage(i) {
    if (i >= count) {
        write("success");
        enableStartButton();
        return;
    }

    myData = myRepository[i];
    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                return;
            }
            loadImage(i+1); 
        }
    );
}

loadImage(0);

ฉันคิดว่าข้อเสียคือชัดเจน: ฉันต้อง rework วนซ้ำในการเรียกซ้ำรหัสที่ควรจะถูกดำเนินการในที่สุดคือที่ไหนสักแห่งในช่วงกลางของฟังก์ชั่นรหัสเริ่มต้นการดาวน์โหลด ( loadImage(0)) อยู่ด้านล่างสุด และโดยทั่วไปการอ่านและติดตามจะยากกว่ามาก มันน่าเกลียดและฉันไม่ชอบมัน

ฉันแน่ใจว่าฉันไม่ใช่คนแรกที่พบปัญหานี้ดังนั้นคำถามของฉันคือ: มีรูปแบบแนวปฏิบัติที่ดีที่สุดที่ฉันสามารถติดตามเพื่อให้โค้ดของฉันอ่านได้ทั้งๆที่ใช้รหัสแบบอะซิงโครนัสและการโทรกลับ


มีเหตุผลพิเศษที่ต้องทำการโทรแบบ "async" ตามลำดับหรือไม่? นี่เป็นโค้ดที่เรียบง่ายของโค้ดอื่นหรือไม่?
Izkata

@Izkata: เหตุผลก็คือฉันต้องการที่จะดีกับเซิร์ฟเวอร์ระยะไกล (= ไม่กระหน่ำยิงด้วยคำขอหลายร้อยรายการพร้อมกัน) มันไม่ใช่ข้อกำหนดที่กำหนดไว้ในหิน ใช่มันเป็นรหัสเวอร์ชั่นที่เรียบง่าย แต่LoadImageAsyncจริงๆแล้วเป็นการเรียกไปยังExt.Ajax.requestSencha Touch
Heinzi

1
เบราว์เซอร์ส่วนใหญ่จะไม่อนุญาตให้คุณตอกย้ำเซิร์ฟเวอร์ต่อไป - พวกเขาเพียงแค่จัดคิวคำขอและเริ่มถัดไปเมื่อหนึ่งในรายการก่อนหน้านี้เสร็จสมบูรณ์
Izkata


พระเจ้า! คำแนะนำที่ไม่ดีมากมายที่นี่ ไม่มีรูปแบบการออกแบบใดที่จะช่วยคุณได้ มองเข้าไปในasync.js , async.waterfallคำตอบของคุณ
Salman von Abbas

คำตอบ:


4

ไม่น่าเป็นไปได้อย่างมากที่คุณจะประสบความสำเร็จได้ด้วย js ธรรมดาในระดับเดียวกับความกระชับและการแสดงออกในการทำงานกับการโทรกลับที่ C # 5 มี คอมไพเลอร์ทำงานในการเขียนข้อมูลทั้งหมดที่สร้างให้คุณและจนกว่า js runtimes จะทำเช่นนั้นคุณจะต้องผ่านการติดต่อกลับเป็นครั้งคราวที่นี่และที่นั่น

แต่คุณอาจไม่เคยต้องการที่จะนำการเรียกกลับลงไปถึงระดับของความเรียบง่ายของโค้ดเชิงเส้น - ฟังก์ชั่นการขว้างปาไปรอบ ๆ ไม่จำเป็นต้องเป็นที่น่าเกลียดคือมีทั้งโลกทำงานกับชนิดของรหัสนี้และพวกเขาให้มีสติโดยไม่ต้องและasyncawait

ตัวอย่างเช่นใช้ฟังก์ชั่นลำดับสูงกว่า (js ของฉันอาจเป็นสนิมเล็กน้อย):

// generic - this is a library function
function iterateAsync(iterator, action, onSuccess, onFailure) {
var item = iterator();
if(item == null) { // exit condition
    onSuccess();
    return;
}
action(item,
    function (success) {
        if(success)
            iterateAsync(iterator, action, onSuccess, onFailure);
        else
            onFailure();
    });
}


// calling code
var currentImage = 0;
var imageCount = 42;

// you know your library function expects an iterator with no params, 
// and an async action with the current item and its continuation as params
iterateAsync(
// this is your iterator
function () {   
    if(currentImage >= imageCount)
        return null;
    return "http://my/server/GetImage?" + (currentImage++);
},

// this is your action - coincidentally, no adaptor for the correct signature is necessary
LoadImageAsync,

// these are your outs
function () { console.log("All OK."); },
function () { console.log("FAILED!"); }
);

2

เอาฉันสักหน่อยเพื่อถอดรหัสว่าทำไมคุณถึงทำแบบนี้ แต่ฉันคิดว่านี่อาจใกล้เคียงกับสิ่งที่คุณต้องการ?

function loadImages() {
   var countRemainingToLoad = 0;
   var failures = 0;

   myRepository.each(function (myData) {
      countRemainingToLoad++;

      LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) {
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                failures++;
            }
            countRemainingToLoad--;
            if (countRemainingToLoad == 0 && failures == 0) {
                enableStartButton();
            }
        }
    );
}

disableStartButton();
loadImages();

ก่อนปิดคำขอ AJAX ให้มากที่สุดเท่าที่จะทำได้พร้อมกันและรอจนกว่าจะเสร็จสมบูรณ์ก่อนที่จะเปิดใช้งานปุ่มเริ่ม นี่จะเร็วกว่าการรอลำดับและฉันคิดว่าง่ายต่อการติดตาม

แก้ไข : โปรดทราบว่าสิ่งนี้ถือว่าคุณมี.each()อยู่และนั่นmyRepositoryคืออาร์เรย์ ระวังการวนซ้ำวนซ้ำที่คุณใช้ที่นี่แทนถ้าไม่พร้อมใช้ - อันนี้ใช้ประโยชน์จากคุณสมบัติการปิดสำหรับการโทรกลับ ฉันไม่แน่ใจว่าสิ่งที่คุณมีอยู่ แต่เนื่องจากLoadImageAsyncดูเหมือนจะเป็นส่วนหนึ่งของห้องสมุดเฉพาะ - ฉันไม่เห็นผลลัพธ์ใน Google


+1, ฉันมี.each()อยู่แล้วและเมื่อคุณพูดถึงแล้วมันไม่จำเป็นต้องทำการโหลดตามลำดับ ฉันจะลองแก้ปัญหาของคุณอย่างแน่นอน (แม้ว่าฉันจะยอมรับคำตอบของ vski เนื่องจากมันใกล้เคียงกับคำถามทั่วไปที่เป็นคำถามทั่วไปมากขึ้น)
Heinzi

@ Heinzi เห็นด้วยกับความแตกต่าง แต่ (ฉันคิดว่า) นี่เป็นตัวอย่างที่ดีว่าภาษาต่าง ๆ มีวิธีที่แตกต่างกันในการจัดการสิ่งเดียวกัน หากสิ่งที่รู้สึกอึดอัดใจเมื่อแปลเป็นภาษาอื่นอาจมีวิธีที่ง่ายกว่าในการแปลโดยใช้กระบวนทัศน์ที่แตกต่าง
Izkata

1

คำเตือน: คำตอบนี้ไม่ได้ตอบปัญหาของคุณโดยเฉพาะมันเป็นคำตอบทั่วไปของคำถาม: "มีรูปแบบการปฏิบัติที่ดีที่สุดที่ฉันสามารถติดตามเพื่อให้โค้ดของฉันอ่านได้ทั้งๆที่ใช้รหัสอะซิงโครนัสและการโทรกลับ"

จากสิ่งที่ฉันรู้ไม่มีรูปแบบ "ดีขึ้น" เพื่อจัดการกับสิ่งนี้ อย่างไรก็ตามฉันได้เห็นวิธีการสองประเภทที่ใช้เพื่อหลีกเลี่ยงฝันร้ายที่เรียกคืนซ้อนกัน

1 / การใช้ฟังก์ชั่นที่มีชื่อแทนการเรียกกลับที่ไม่ระบุชื่อ

    function start() {
        mongo.findById( id, handleDatas );
    }

    function handleDatas( datas ) {
        // Handle the datas returned.
    }

วิธีนี้คุณหลีกเลี่ยงการซ้อนโดยการส่งตรรกะของฟังก์ชันที่ไม่ระบุชื่อในฟังก์ชันอื่น

2 / การใช้ไลบรารีการจัดการการไหล ฉันชอบที่จะใช้ขั้นตอนแต่เป็นเพียงเรื่องของการตั้งค่า มันเป็นหนึ่งใน LinkedIn ที่ใช้โดยวิธีการ

    Step( {
        function start() {
            // the "this" magically sends to the next function.
            mongo.findById( this );
        },

        function handleDatas( el ) {
            // Handle the datas.
            // Another way to use it is by returning a value,
            // the value will be sent to the next function.
            // However, this is specific to Step, so look at
            // the documentation of the library you choose.
            return value;
        },

        function nextFunction( value ) {
            // Use the returned value from the preceding function
        }
    } );

ฉันใช้ไลบรารี่การจัดการโฟลว์เมื่อฉันใช้การโทรกลับหลายระดับซ้อนกันเพราะสามารถอ่านได้มากขึ้นเมื่อมีการใช้รหัสจำนวนมาก


0

ใส่เพียงแค่ JavaScript awaitไม่ได้มีน้ำตาลประโยคของ
แต่การย้ายส่วน "สิ้นสุด" ลงในด้านล่างของฟังก์ชั่นนั้นง่าย และด้วยการใช้งานฟังก์ชั่นแบบไม่ระบุชื่อเราสามารถหลีกเลี่ยงการประกาศการอ้างอิงได้

disableStartButton();

(function(i, count) {
    var loadImage = arguments.callee;
    myData = myRepository[i];

    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (!success) {
                write("error loading image " + myData.Id);

            } else {
                myData.Image = data;
                if (i < count) {
                    loadImage(i + 1, count);

                } else {
                    write("success");
                    enableStartButton();
                    return;

                }

            }

        }
    );
})(0, myRepository.length);

นอกจากนี้คุณยังสามารถส่งส่วน "สิ้นสุด" เป็นความสำเร็จในการติดต่อกลับไปยังฟังก์ชัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.