จะตัดการเรียกฟังก์ชัน async ลงในฟังก์ชัน sync ใน Node.js หรือ Javascript ได้อย่างไร


122

สมมติว่าคุณรักษาห้องสมุดที่ exposes getDataฟังก์ชัน ผู้ใช้ของคุณเรียกสิ่งนี้ว่ารับข้อมูลจริง: ข้อมูล
var output = getData();
Under the hood จะถูกบันทึกไว้ในไฟล์เพื่อให้คุณใช้งานgetDataโดยใช้ Node.js ในfs.readFileSyncตัว เห็นได้ชัดทั้งคู่getDataและfs.readFileSyncเป็นฟังก์ชันการซิงค์ วันหนึ่งคุณได้รับคำสั่งให้เปลี่ยนแหล่งข้อมูลพื้นฐานเป็น repo เช่น MongoDB ซึ่งสามารถเข้าถึงได้แบบอะซิงโครนัสเท่านั้น นอกจากนี้คุณยังได้รับคำสั่งให้หลีกเลี่ยงการทำให้ผู้ใช้ของคุณgetDataขุ่นเคืองไม่สามารถเปลี่ยน API ให้กลับมาเป็นเพียงคำสัญญาหรือเรียกร้องพารามิเตอร์การเรียกกลับ คุณมีคุณสมบัติตามข้อกำหนดทั้งสองอย่างไร

ฟังก์ชันอะซิงโครนัสโดยใช้การเรียกกลับ / สัญญาคือ DNA ของ JavasSript และ Node.js แอป JS ที่ไม่สำคัญใด ๆ อาจจะเต็มไปด้วยรูปแบบการเข้ารหัสนี้ แต่การปฏิบัตินี้สามารถนำไปสู่สิ่งที่เรียกว่าปิรามิดแห่งการลงโทษได้อย่างง่ายดาย ยิ่งไปกว่านั้นหากรหัสใด ๆ ในผู้โทรใด ๆ ในสายการโทรขึ้นอยู่กับผลลัพธ์ของฟังก์ชัน async รหัสเหล่านั้นจะต้องถูกรวมไว้ในฟังก์ชันการโทรกลับเช่นกันซึ่งเป็นการกำหนดข้อ จำกัด รูปแบบการเข้ารหัสสำหรับผู้โทร ในบางครั้งฉันพบว่าจำเป็นต้องห่อหุ้มฟังก์ชัน async (มักมีให้ในไลบรารีของบุคคลที่สาม) ลงในฟังก์ชันการซิงค์เพื่อหลีกเลี่ยงการแยกตัวประกอบซ้ำทั่วโลกจำนวนมาก การค้นหาวิธีแก้ปัญหาในเรื่องนี้มักจะลงเอยด้วยNode Fibersหรือแพ็คเกจ npm ที่ได้มาจากมัน แต่เส้นใยไม่สามารถแก้ปัญหาที่ฉันเผชิญได้ แม้แต่ตัวอย่างที่จัดทำโดยผู้เขียนของ Fibers ก็แสดงให้เห็นถึงข้อบกพร่อง:

...
Fiber(function() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);
}).run();
console.log('back in main');

เอาต์พุตจริง:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)

หากฟังก์ชัน Fiber เปลี่ยนฟังก์ชั่น async เป็น sync จริงๆผลลัพธ์ควรเป็น:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
back in main

ฉันได้สร้างตัวอย่างง่ายๆอีกอย่างในJSFiddleและกำลังมองหาโค้ดเพื่อให้ได้ผลลัพธ์ที่คาดหวัง ฉันยอมรับโซลูชันที่ใช้งานได้เฉพาะใน Node.js ดังนั้นคุณมีอิสระที่จะต้องใช้แพ็คเกจ npm ใด ๆ แม้ว่าจะไม่ทำงานใน JSFiddle ก็ตาม


2
ฟังก์ชัน Async ไม่สามารถทำซิงโครนัสในโหนดได้และแม้ว่าจะทำได้คุณก็ไม่ควรทำเช่นนั้น ปัญหาคือในโมดูล fs คุณสามารถเห็นฟังก์ชันที่แยกจากกันโดยสิ้นเชิงสำหรับการเข้าถึงระบบไฟล์แบบซิงโครนัสและอะซิงโครนัส สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือปิดบังลักษณะที่ปรากฏของ async ด้วยสัญญาหรือโครูทีน (เครื่องกำเนิดไฟฟ้าใน ES6) สำหรับการจัดการปิรามิดการเรียกกลับให้ตั้งชื่อแทนการกำหนดในการเรียกใช้ฟังก์ชันและใช้บางอย่างเช่นไลบรารี async
qubyte

8
สำหรับ dandavis async จะเพิ่มรายละเอียดการใช้งานไปยัง call chain ซึ่งบางครั้งก็บังคับให้ทำการ refactoring ทั่วโลก สิ่งนี้เป็นอันตรายและเป็นหายนะสำหรับการใช้งานที่ซับซ้อนซึ่งการแยกส่วนและการกักกันเป็นสิ่งสำคัญ
abbr

4
"พีระมิดเรียกกลับแห่งการลงโทษ" เป็นเพียงตัวแทนของปัญหาเท่านั้น สัญญาสามารถซ่อนหรืออำพรางได้ แต่ไม่สามารถจัดการกับความท้าทายที่แท้จริงได้: หากผู้เรียกใช้ฟังก์ชัน async ขึ้นอยู่กับผลลัพธ์ของฟังก์ชัน async จะต้องใช้การโทรกลับและผู้โทรก็เช่นกันเป็นต้นนี่เป็นตัวอย่างคลาสสิกของการกำหนดข้อ จำกัด ในการ ผู้โทรเพียงเพราะรายละเอียดการใช้งาน
abbr

1
@abbr: ขอบคุณสำหรับโมดูล deasync คำอธิบายปัญหาของคุณคือสิ่งที่ฉันกำลังมองหาและไม่พบวิธีแก้ปัญหาใด ๆ ฉันยุ่งกับเครื่องกำเนิดไฟฟ้าและการวนซ้ำ แต่ก็ได้ข้อสรุปเช่นเดียวกับคุณ
Kevin Jhangiani

2
เป็นที่น่าสังเกตว่าแทบไม่เคยเป็นความคิดที่ดีเลยที่จะบังคับให้ฟังก์ชัน async ทำการซิงค์ คุณมักจะมีวิธีแก้ปัญหาที่ดีกว่าซึ่งทำให้ความไม่ตรงกันของฟังก์ชันเหมือนเดิมในขณะที่ยังคงได้ผลเช่นเดียวกัน (เช่นการจัดลำดับการตั้งค่าตัวแปร ฯลฯ )
Madara's Ghost

คำตอบ:


105

deasyncเปลี่ยนฟังก์ชัน async เป็นการซิงค์นำไปใช้กับกลไกการบล็อกโดยการเรียก Node.js event loop ที่เลเยอร์ JavaScript ด้วยเหตุนี้ deasync จึงบล็อกโค้ดที่ตามมาไม่ให้ทำงานโดยไม่บล็อกเธรดทั้งหมดและไม่ต้องรอให้วุ่นวาย ด้วยโมดูลนี้นี่คือคำตอบสำหรับความท้าทาย jsFiddle:

function AnticipatedSyncFunction(){
  var ret;
  setTimeout(function(){
      ret = "hello";
  },3000);
  while(ret === undefined) {
    require('deasync').runLoopOnce();
  }
  return ret;    
}


var output = AnticipatedSyncFunction();
//expected: output=hello (after waiting for 3 sec)
console.log("output="+output);
//actual: output=hello (after waiting for 3 sec)

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


มีใครโชคดีบ้างไหม? ฉันไม่สามารถทำให้มันทำงานได้
น้องใหม่

3
ฉันไม่สามารถทำให้มันทำงานได้อย่างถูกต้อง คุณควรปรับปรุงเอกสารของคุณสำหรับโมดูลนี้หากคุณต้องการให้มีการใช้งานมากขึ้น ฉันสงสัยว่าผู้เขียนรู้แน่ชัดว่าการแบ่งส่วนสำหรับการใช้โมดูลนั้นเป็นอย่างไรและถ้าเป็นเช่นนั้นพวกเขาก็ไม่ได้บันทึกไว้อย่างแน่นอน
Alexander Mills

5
จนถึงขณะนี้มีปัญหาหนึ่งที่ได้รับการยืนยันแล้วในตัวติดตามปัญหาของ github ปัญหาได้รับการแก้ไขแล้วใน Node v0.12 ส่วนที่เหลือที่ฉันรู้เป็นเพียงการคาดเดาที่ไร้เหตุผลซึ่งไม่คุ้มค่าที่จะบันทึกไว้ หากคุณเชื่อว่าปัญหาของคุณเกิดจาก deasync โพสต์สถานการณ์จำลองที่มีอยู่ในตัวเองและเราจะตรวจสอบ
abbr

ฉันพยายามใช้มันและได้รับการปรับปรุงบางอย่างในสคริปต์ของฉัน แต่ฉันก็ยังไม่มีโชคกับวันที่ ฉันแก้ไขโค้ดดังต่อไปนี้: function AnticipatedSyncFunction(){ var ret; setTimeout(function(){ var startdate = new Date() //console.log(startdate) ret = "hello" + startdate; },3000); while(ret === undefined) { require('deasync').runLoopOnce(); } return ret; } var output = AnticipatedSyncFunction(); var startdate = new Date() console.log(startdate) console.log("output="+output); และฉันคาดว่าจะเห็นผลลัพธ์ที่แตกต่างกัน 3 วินาที!
Alex

@abbr สามารถใช้เบราว์เซอร์และใช้งานได้โดยไม่ต้องพึ่งพาโหนด>
คานธี

5

นอกจากนี้ยังมีโมดูลการซิงค์ npm ซึ่งใช้สำหรับซิงโครไนซ์กระบวนการดำเนินการสืบค้น

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

โค้ดตัวอย่าง

/*require sync module*/
var Sync = require('sync');
    app.get('/',function(req,res,next){
      story.find().exec(function(err,data){
        var sync_function_data = find_user.sync(null, {name: "sanjeev"});
          res.send({story:data,user:sync_function_data});
        });
    });


    /*****sync function defined here *******/
    function find_user(req_json, callback) {
        process.nextTick(function () {

            users.find(req_json,function (err,data)
            {
                if (!err) {
                    callback(null, data);
                } else {
                    callback(null, err);
                }
            });
        });
    }

ลิงค์อ้างอิง: https://www.npmjs.com/package/sync


4

ถ้าฟังก์ชัน Fiber เปลี่ยนฟังก์ชัน async sleep ให้เป็น sync จริงๆ

ใช่. okภายในเส้นใยฟังก์ชั่นรอก่อนการเข้าสู่ระบบ เส้นใยไม่ทำให้ฟังก์ชัน async ซิงโครนัส แต่อนุญาตให้เขียนโค้ดที่มีลักษณะซิงโครนัสซึ่งใช้ฟังก์ชัน async จากนั้นจะทำงานแบบอะซิงโครนัสภายในไฟล์Fiber.

ในบางครั้งฉันพบว่าจำเป็นต้องห่อหุ้มฟังก์ชัน async ไว้ในฟังก์ชันการซิงค์เพื่อหลีกเลี่ยงการแยกตัวประกอบซ้ำทั่วโลก

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

วัตถุประสงค์ของฉันคือเพื่อลดผลกระทบต่อผู้โทรเมื่อวิธีการรับข้อมูลเปลี่ยนจากการซิงค์เป็น async

ทั้งคำสัญญาและเส้นใยสามารถทำเช่นนั้นได้


1
นี่คือสิ่งที่เลวร้ายที่สุดที่คุณสามารถทำได้กับ Node.js: "โค้ดที่มีลักษณะซิงโครนัสซึ่งใช้ฟังก์ชันแบบอะซิงโครนัสจากนั้นจะทำงานแบบอะซิงโครนัส" หาก API ของคุณทำเช่นนั้นคุณจะทำลายชีวิต หากเป็นแบบอะซิงโครนัสควรต้องมีการโทรกลับและเกิดข้อผิดพลาดหากไม่มีการโทรกลับ นั่นเป็นวิธีที่ดีที่สุดในการสร้าง API เว้นแต่เป้าหมายของคุณคือการหลอกลวงผู้อื่น
Alexander Mills

@AlexMills: ใช่มันจะน่ากลัวอย่างแน่นอน อย่างไรก็ตามโชคดีที่นี่ไม่ใช่สิ่งที่ API สามารถทำได้ API แบบอะซิงโครนัสจำเป็นต้องยอมรับการโทรกลับ / ส่งคืนคำสัญญาเสมอ / คาดว่าจะทำงานภายในไฟเบอร์ - ไม่สามารถทำงานได้หากไม่มี Afaik เส้นใยส่วนใหญ่จะใช้ในสคริปต์ quick'n'dirty ที่บล็อกและไม่มีการทำงานพร้อมกัน แต่ต้องการใช้ async API เช่นเดียวกับในโหนดบางครั้งมีบางกรณีที่คุณใช้fsวิธีซิงโครนัส
Bergi

2
โดยทั่วไปฉันชอบโหนด โดยเฉพาะอย่างยิ่งถ้าฉันสามารถใช้ typescript แทน pure js แต่เรื่องไร้สาระแบบอะซิงก์ทั้งหมดนี้ที่แทรกซึมทุกสิ่งที่คุณทำและติดเชื้อทุกฟังก์ชั่นในสายการโทรทันทีที่คุณตัดสินใจโทรแบบไม่ซิงค์ครั้งเดียวเป็นสิ่งที่ฉัน ... เกลียดจริงๆ Async api เป็นเหมือนโรคติดเชื้อสายหนึ่งจะติดเชื้อฐานรหัสทั้งหมดของคุณบังคับให้คุณเขียนรหัสทั้งหมดที่คุณมีใหม่ ฉันไม่เข้าใจจริงๆว่าใคร ๆ ก็สามารถโต้แย้งได้ว่านี่เป็นสิ่งที่ดี
Kris

@Kris Node ใช้โมเดลอะซิงโครนัสสำหรับงาน IO เพราะมันรวดเร็วและเรียบง่าย คุณสามารถทำหลายอย่างพร้อมกันได้เช่นกัน แต่การบล็อกจะช้าเนื่องจากคุณไม่สามารถทำอะไรได้พร้อมกันเว้นแต่คุณจะไปหาเธรดซึ่งทำให้ทุกอย่างซับซ้อน
Bergi

@Bergi ฉันอ่านแถลงการณ์ดังนั้นฉันจึงรู้ข้อโต้แย้ง แต่การเปลี่ยนรหัสที่มีอยู่ของคุณเป็น async ทันทีที่คุณกดเรียก api ครั้งแรกที่ไม่มีการซิงค์เทียบเท่านั้นไม่ใช่เรื่องง่าย ทุกอย่างแตกและโค้ดทุกบรรทัดจะต้องได้รับการพิจารณา ฉันรับประกันว่า ... จะใช้เวลาสักครู่ในการแปลงและทำให้มันใช้งานได้อีกครั้งหลังจากแปลงสิ่งทั้งหมดเป็น async idiom
Kris

2

คุณต้องใช้คำสัญญา:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async () => {
    return await asyncOperation();
}

const topDog = () => {
    asyncFunction().then((res) => {
        console.log(res);
    });
}

ฉันชอบคำจำกัดความของฟังก์ชันลูกศรมากกว่า แต่สตริงใด ๆ ของรูปแบบ "() => {... }" สามารถเขียนเป็น "function () {... }"

ดังนั้น topDog จึงไม่ async แม้ว่าจะเรียกใช้ฟังก์ชัน async ก็ตาม

ใส่คำอธิบายภาพที่นี่

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

const getDemSweetDataz = (req, res) => {
    (async () => {
        try{
            res.status(200).json(
                await asyncOperation()
            );
        }
        catch(e){
            res.status(500).json(serviceResponse); //or whatever
        }
    })() //So we defined and immediately called this async function.
}

เมื่อใช้สิ่งนี้กับการโทรกลับคุณสามารถทำคำสัญญาที่ไม่ใช้สัญญาได้:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async (callback) => {
    let res = await asyncOperation();
    callback(res);
}

const topDog = () => {
    let callback = (res) => {
        console.log(res);
    };

    (async () => {
        await asyncFunction(callback)
    })()
}

โดยใช้เคล็ดลับนี้กับ EventEmitter คุณจะได้รับผลลัพธ์เดียวกัน กำหนด Listener ของ EventEmitter ที่ฉันกำหนดการโทรกลับและปล่อยเหตุการณ์ที่ฉันโทรกลับ


1

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

มาดูตัวอย่าง:สมมติว่าคุณใช้เฟรมเวิร์กซึ่งเป็นจุดเริ่มต้นของแอปพลิเคชันของคุณ (คุณไม่สามารถแก้ไขเฟรมเวิร์กนี้ได้) เฟรมเวิร์กนี้โหลดโมดูล nodejs เป็นปลั๊กอินและเรียกใช้เมธอดบางอย่างบนปลั๊กอิน สมมติว่าเฟรมเวิร์กนี้ยอมรับเฉพาะฟังก์ชันซิงโครนัสและไม่ใช้เส้นใยด้วยตัวเอง

มีไลบรารีที่คุณต้องการใช้ในหนึ่งในปลั๊กอินของคุณ แต่ไลบรารีนี้เป็นแบบ async และคุณไม่ต้องการแก้ไขด้วย

เธรดหลักไม่สามารถให้ผลลัพธ์ได้เมื่อไม่มีไฟเบอร์ทำงาน แต่คุณยังสามารถสร้างปลั๊กอินโดยใช้เส้นใยได้! เพียงแค่สร้างรายการ Wrapper ที่เริ่มต้นเฟรมเวิร์กทั้งหมดภายในไฟเบอร์คุณจึงสามารถดำเนินการจากปลั๊กอินได้

ข้อเสีย: หากเฟรมเวิร์กใช้setTimeoutหรือPromises ภายในมันจะหนีบริบทไฟเบอร์ นี้สามารถทำงานรอบโดยเยาะเย้ยsetTimeout, Promise.thenและทุกจัดการเหตุการณ์

นี่คือวิธีที่คุณจะได้เส้นใยจนกว่า a Promiseจะได้รับการแก้ไข รหัสนี้ใช้ฟังก์ชัน async (Promise return) และเรียกใช้ไฟเบอร์ต่อเมื่อคำสัญญาได้รับการแก้ไข:

กรอบ entry.js

console.log(require("./my-plugin").run());

async-lib.js

exports.getValueAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Async Value");
    }, 100);
  });
};

-plugin.js ของฉัน

const Fiber = require("fibers");

function fiberWaitFor(promiseOrValue) {
  var fiber = Fiber.current, error, value;
  Promise.resolve(promiseOrValue).then(v => {
    error = false;
    value = v;
    fiber.run();
  }, e => {
    error = true;
    value = e;
    fiber.run();
  });
  Fiber.yield();
  if (error) {
    throw value;
  } else {
    return value;
  }
}

const asyncLib = require("./async-lib");

exports.run = () => {
  return fiberWaitFor(asyncLib.getValueAsync());
};

-entry.js ของฉัน

require("fibers")(() => {
  require("./framework-entry");
}).run();

เมื่อคุณเรียกใช้node framework-entry.jsจะทำให้เกิดข้อผิดพลาด: Error: yield() called with no fiber running. หากคุณเรียกใช้node my-entry.jsงานได้ตามที่คาดไว้


0

การซิงค์โค้ด Node.js มีความจำเป็นในบางด้านเช่นฐานข้อมูล แต่ข้อดีที่แท้จริงของ Node.js อยู่ในรหัส async เนื่องจากเป็นเธรดเดียวจึงไม่บล็อก

เราสามารถซิงค์โดยใช้ฟังก์ชันที่สำคัญ Fiber () ใช้ await () และ defer () เราเรียกวิธีการทั้งหมดโดยใช้ await () จากนั้นแทนที่ฟังก์ชันเรียกกลับด้วย defer ()

รหัส Async ปกติสิ่งนี้ใช้ฟังก์ชัน CallBack

function add (var a, var b, function(err,res){
       console.log(res);
});

 function sub (var res2, var b, function(err,res1){
           console.log(res);
    });

 function div (var res2, var b, function(err,res3){
           console.log(res3);
    });

ซิงค์รหัสด้านบนโดยใช้ Fiber (), await () และ defer ()

fiber(function(){
     var obj1 = await(function add(var a, var b,defer()));
     var obj2 = await(function sub(var obj1, var b, defer()));
     var obj3 = await(function sub(var obj2, var b, defer()));

});

ฉันหวังว่านี่จะช่วยได้ ขอบคุณ


0

ปัจจุบันรูปแบบเครื่องกำเนิดไฟฟ้านี้สามารถแก้ปัญหาได้ในหลายสถานการณ์

นี่คือตัวอย่างของพรอมต์คอนโซลตามลำดับใน nodejs โดยใช้ฟังก์ชัน async readline.question:

var main = (function* () {

  // just import and initialize 'readline' in nodejs
  var r = require('readline')
  var rl = r.createInterface({input: process.stdin, output: process.stdout })

  // magic here, the callback is the iterator.next
  var answerA = yield rl.question('do you want this? ', r=>main.next(r))    

  // and again, in a sync fashion
  var answerB = yield rl.question('are you sure? ', r=>main.next(r))        

  // readline boilerplate
  rl.close()

  console.log(answerA, answerB)

})()  // <-- executed: iterator created from generator
main.next()     // kick off the iterator, 
                // runs until the first 'yield', including rightmost code
                // and waits until another main.next() happens

-1

คุณไม่ควรมองไปที่สิ่งที่เกิดขึ้นรอบ ๆการโทรที่สร้างเส้นใย แต่ดูว่าเกิดอะไรขึ้นภายในเส้นใย เมื่อคุณอยู่ในไฟเบอร์คุณสามารถตั้งโปรแกรมในรูปแบบการซิงค์ได้ ตัวอย่างเช่น:

ฟังก์ชัน f1 () {
    console.log ('รอ ... ' + วันที่ใหม่);
    การนอนหลับ (1000)
    console.log ('ตกลง ... ' + วันที่ใหม่);   
}

ฟังก์ชัน f2 () {
    f1 ();
    f1 ();
}

ไฟเบอร์ (ฟังก์ชัน () {
    F2 ();
}).วิ่ง();

ภายในเส้นใยที่คุณโทรf1, f2และsleepราวกับว่าพวกเขาซิงค์

ในเว็บแอปพลิเคชันทั่วไปคุณจะสร้าง Fiber ในโปรแกรมจัดการคำขอ HTTP ของคุณ เมื่อคุณทำเสร็จแล้วคุณสามารถเขียนตรรกะการจัดการคำขอทั้งหมดของคุณในรูปแบบการซิงค์แม้ว่าจะเรียกใช้ฟังก์ชัน async (fs, ฐานข้อมูล ฯลฯ )


ขอบคุณ Bruno แต่ถ้าฉันต้องการรูปแบบการซิงค์ในรหัส bootstrap ที่ต้องดำเนินการก่อนที่เซิร์ฟเวอร์จะเชื่อมโยงกับพอร์ต tcp เช่น config หรือข้อมูลที่ต้องอ่านจาก db ที่เปิด async ฉันอาจลงเอยด้วยการตัดเซิร์ฟเวอร์ js ทั้งหมดใน Fiber และฉันสงสัยว่าจะฆ่าการทำงานพร้อมกันในระดับกระบวนการทั้งหมด อย่างไรก็ตามเป็นข้อเสนอแนะที่ควรค่าแก่การตรวจสอบ สำหรับฉันแล้วทางออกที่ดีควรจะสามารถรวมฟังก์ชัน async เพื่อจัดเตรียมไวยากรณ์การโทรแบบซิงค์และบล็อกโค้ดบรรทัดถัดไปในห่วงโซ่ผู้โทรโดยไม่ต้องเสียสละการทำงานพร้อมกันในระดับกระบวนการ
abbr

คุณสามารถรวมรหัส bootstrap ทั้งหมดไว้ในสาย Fiber ขนาดใหญ่ การทำงานพร้อมกันไม่น่าจะเป็นปัญหาเพราะโดยปกติแล้วรหัสบูตสแตรปจะต้องทำงานให้เสร็จสิ้นก่อนที่คุณจะเริ่มให้บริการคำขอ นอกจากนี้เส้นใยไม่ได้ป้องกันไม่ให้เส้นใยอื่น ๆ วิ่ง: ทุกครั้งที่คุณได้รับผลตอบแทนคุณจะให้โอกาสเส้นใยอื่น ๆ (และเธรดหลัก) ในการทำงาน
Bruno Jouhier

ฉันได้ห่อ Express bootstrap file server.js ด้วยไฟเบอร์ ลำดับการดำเนินการคือสิ่งที่ฉันกำลังมองหา แต่การตัดนั้นไม่มีผลใด ๆ กับตัวจัดการคำขอ ดังนั้นฉันเดาว่าต้องใช้ wrapper เดียวกันกับผู้มอบหมายงานแต่ละคน ฉันยอมแพ้ในตอนนี้เพราะดูเหมือนจะไม่ได้ผลดีไปกว่าที่จะช่วยหลีกเลี่ยงการแยกตัวประกอบใหม่ทั่วโลก วัตถุประสงค์ของฉันคือลดผลกระทบต่อผู้โทรเมื่อวิธีการรับข้อมูลเปลี่ยนจากการซิงค์เป็น async ในเลเยอร์ DAO และ Fiber ยังคงอยู่ในความท้าทายเล็กน้อย
abbr

@fred: การ "ซิงโครไนซ์" สตรีมเหตุการณ์เช่นตัวจัดการคำขอไม่สมเหตุสมผลมากนักคุณจำเป็นต้องมีการwhile(true) handleNextRequest()วนซ้ำ การห่อตัวจัดการคำขอแต่ละรายการในเส้นใยจะ
Bergi

@fred: fibre ไม่ได้ช่วยอะไรคุณมากนักกับ Express เพราะการโทรกลับของ Express ไม่ใช่การโทรกลับแบบต่อเนื่อง (การโทรกลับที่มักจะเรียกเพียงครั้งเดียวไม่ว่าจะด้วยข้อผิดพลาดหรือผลลัพธ์ก็ตาม) แต่เส้นใยจะแก้ปัญหาปิรามิดแห่งการลงโทษเมื่อคุณมีโค้ดจำนวนมากที่เขียนไว้ด้านบนของ async API ที่มีการเรียกกลับอย่างต่อเนื่อง (เช่น fs, mongodb และอื่น ๆ อีกมากมาย)
Bruno Jouhier

-2

ตอนแรกฉันพยายามดิ้นรนกับ node.js และ async.js เป็นไลบรารีที่ดีที่สุดที่ฉันพบเพื่อช่วยคุณจัดการกับสิ่งนี้ หากคุณต้องการเขียนโค้ดซิงโครนัสด้วยโหนดวิธีการคือวิธีนี้

var async = require('async');

console.log('in main');

doABunchOfThings(function() {
  console.log('back in main');
});

function doABunchOfThings(fnCallback) {
  async.series([
    function(callback) {
      console.log('step 1');
      callback();
    },
    function(callback) {
      setTimeout(callback, 1000);
    },
    function(callback) {
      console.log('step 2');
      callback();
    },
    function(callback) {
      setTimeout(callback, 2000);
    },
    function(callback) {
      console.log('step 3');
      callback();
    },
  ], function(err, results) {
    console.log('done with things');
    fnCallback();
  });
}

โปรแกรมนี้จะผลิตสิ่งต่อไปนี้เสมอ ...

in main
step 1
step 2
step 3
done with things
back in main

2
asyncทำงานในตัวอย่าง b / c ของmainคุณซึ่งไม่สนใจผู้โทร ลองนึกภาพโค้ดทั้งหมดของคุณรวมอยู่ในฟังก์ชันซึ่งควรจะส่งคืนผลลัพธ์ของการเรียกฟังก์ชัน async ของคุณ สามารถพิสูจน์ได้อย่างง่ายดายว่าไม่ทำงานโดยเพิ่มconsole.log('return');ที่ส่วนท้ายของรหัสของคุณ ในกรณีเช่นนี้การส่งออกของreturnที่จะเกิดขึ้นหลังจากที่แต่ก่อนin mainstep 1
abbr

-11

Javascript เป็นภาษาเธรดเดียวคุณไม่ต้องการบล็อกเซิร์ฟเวอร์ทั้งหมดของคุณ! รหัส Async กำจัดเงื่อนไขการแข่งขันโดยการทำให้การอ้างอิงชัดเจน

เรียนรู้ที่จะรักรหัสอะซิงโครนัส!

ดูpromisesรหัสอะซิงโครนัสโดยไม่ต้องสร้างปิรามิดแห่งนรกเรียกกลับ ฉันแนะนำไลบรารี promQ สำหรับ node.js

httpGet(url.parse("http://example.org/")).then(function (res) {
    console.log(res.statusCode);  // maybe 302
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);  // maybe 200
});

http://howtonode.org/promises

แก้ไข: นี่เป็นคำตอบที่ถกเถียงกันมากที่สุดของฉันตอนนี้โหนดมีคีย์เวิร์ดที่ให้ผลตอบแทนซึ่งช่วยให้คุณจัดการโค้ดอะซิงโครนัสราวกับว่ามันเป็นซิงโครนัส http://blog.alexmaccaw.com/how-yield-will-transform-node


1
สัญญาว่าจะเปลี่ยนวลีเรียกกลับเท่านั้นแทนที่จะเปลี่ยนฟังก์ชันเป็นการซิงค์
abbr

2
คุณไม่ต้องการให้ซิงค์หรือเซิร์ฟเวอร์ทั้งหมดของคุณจะบล็อก! stackoverflow.com/questions/17959663/…
roo2

1
สิ่งที่พึงปรารถนาคือการโทรซิงค์โดยไม่ปิดกั้นเหตุการณ์อื่น ๆ เช่นคำขออื่นที่จัดการโดย Node.js ฟังก์ชัน Sync ตามคำจำกัดความหมายความว่าจะไม่กลับไปยังผู้โทรจนกว่าผลลัพธ์จะถูกสร้างขึ้น (ไม่ใช่แค่คำสัญญา) ไม่รวมเซิร์ฟเวอร์ไว้ล่วงหน้าจากการจัดการเหตุการณ์อื่น ๆ ในขณะที่การโทรถูกบล็อก
abbr

@fred: ผมคิดว่าคุณกำลังหายไปจุดของสัญญา พวกเขาไม่ได้เป็นเพียงรูปแบบนามธรรมของผู้สังเกตการณ์เท่านั้น แต่มีวิธีเชื่อมโยงและประกอบการกระทำแบบอะซิงโครนัส
Bergi

1
@Bergi ฉันใช้สัญญามากและรู้ว่ามันทำอะไร สิ่งที่ทำได้อย่างมีประสิทธิภาพคือการแยกการเรียกใช้ฟังก์ชัน async เดียวออกเป็นการเรียกใช้ / คำสั่งหลายรายการ แต่จะไม่เปลี่ยนผลลัพธ์ - เมื่อผู้โทรกลับมาจะไม่สามารถส่งคืนผลลัพธ์ของฟังก์ชัน async ได้ ลองดูตัวอย่างที่ฉันโพสต์ใน JSFiddle ผู้เรียกในกรณีนั้นคือฟังก์ชัน AnticipatedSyncFunction และฟังก์ชัน async คือ setTimeout หากคุณสามารถตอบคำท้าทายของฉันโดยใช้คำสัญญาโปรดแสดงให้ฉันเห็น
abbr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.