ฉันจะสร้างฟังก์ชั่นอะซิงโครนัสใน Javascript ได้อย่างไร


143

ลองดูรหัสนี้:

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

อย่างที่คุณเห็นในคอนโซลฟังก์ชั่น "animate" นั้นเป็นแบบอะซิงโครนัสและเป็น "การไหล" ของโค้ดบล็อกตัวจัดการเหตุการณ์ ในความเป็นจริง :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

ติดตามการไหลของรหัสบล็อก!

หากฉันต้องการสร้างfunction asyncFunct() { }ด้วยพฤติกรรมนี้ฉันจะใช้ javascript / jquery ได้อย่างไร ผมคิดว่ามีกลยุทธ์โดยไม่ต้องใช้ที่ setTimeout()


ดูแหล่งข้อมูล jQuery :)
yatskevich

mathod .animate () ใช้การโทรกลับ ภาพเคลื่อนไหวจะโทรกลับเมื่อมีการเคลื่อนไหว หากคุณต้องการพฤติกรรมเดียวกัน. animate () สิ่งที่คุณต้องการคือการติดต่อกลับ (เรียกโดยฟังก์ชั่น "main" หลังจากการดำเนินการอื่น ๆ ) มันแตกต่างกันถ้าคุณต้องการฟังก์ชั่น async "เต็ม" (ฟังก์ชั่นที่เรียกว่า withouth บล็อกการไหลของการดำเนินการ) ในกรณีนี้คุณสามารถใช้ setTimeout () ด้วยความล่าช้า 0 ใกล้
Fabio Buda

@Fabio Buda: ทำไมการโทรกลับ () ควรใช้ async แบบใดแบบหนึ่ง อันที่จริงแล้วมันไม่ได้jsfiddle.net/5H9XT/9
markzzz

อันที่จริงหลังจาก "callback" ฉันอ้างถึงวิธีการแบบ "เต็ม" กับ setTimeout ฉันหมายถึงการเรียกกลับเป็นหลอก async ในทางที่ฟังก์ชั่นที่เรียกว่าหลังโค้ดอื่น ๆ :-)
ฟาบิโอบูดา

คำตอบ:


185

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

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • HTML5 APIs บางตัวเช่น File API, Web Database API
  • เทคโนโลยีที่รองรับ onload
  • ... อื่น ๆ อีกมากมาย

ในความเป็นจริงสำหรับภาพเคลื่อนไหว jQuery ใช้ setInterval


1
ฉันกำลังคุยเรื่องนี้กับเพื่อนเมื่อวานนี้ดังนั้นคำตอบนี้จึงสมบูรณ์แบบ! ฉันเข้าใจและสามารถระบุฟังก์ชั่น async และใช้ใน JS อย่างถูกต้อง แต่ทำไมเราไม่สามารถใช้สิ่งที่กำหนดเองได้ไม่ชัดเจนสำหรับฉัน มันเหมือนกับกล่องดำที่เรารู้ว่าทำให้มันทำงานได้อย่างไร (โดยใช้พูดsetInterval) แต่เราไม่สามารถแม้แต่เปิดมันเพื่อดูว่ามันทำอย่างไร คุณมีข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้หรือไม่?
Matheus Felipe

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

10
@MatheusFelipe youtu.be/8aGhZQkoFbQพูดคุยที่ดีที่สุดเกี่ยวกับหัวข้อนี้ ...
Andreas Niedermair

การดำเนินการบางอย่างโดยเฉพาะอย่างยิ่ง Node.js สนับสนุนsetImmediate
Jon Surrell

promisesสิ่งที่เกี่ยวกับ มันให้awaitableหรือไม่
Nithin Chandran

69

คุณสามารถใช้ตัวจับเวลา:

setTimeout( yourFn, 0 );

(ที่yourFnอ้างอิงถึงฟังก์ชั่นของคุณ)

หรือกับLodash :

_.defer( yourFn );

Defers เรียกใช้funcจนกว่าการเรียกสแต็กปัจจุบันจะถูกล้าง ข้อโต้แย้งเพิ่มเติมใด ๆ ที่มีให้funcเมื่อมันถูกเรียก


3
ใช้งานไม่ได้ฟังก์ชั่นจาวาสคริปต์ที่วาดบนผืนผ้าใบทำให้ UI ไม่ตอบสนอง
gab06

2
@ gab06 - ฉันจะบอกว่าฟังก์ชั่นการวาดภาพผ้าใบของคุณกำลังปิดกั้นด้วยเหตุผลที่ดีของตัวเอง แบ่งการกระทำออกเป็นขนาดเล็กลงและเรียกใช้แต่ละอันด้วยตัวจับเวลา: คุณจะเห็นว่าอินเทอร์เฟซแบบนี้ตอบสนองต่อการคลิกเมาส์ของคุณ ฯลฯ
Marco Faustinelli

ลิงก์เสีย
JulianSoto

1
@JulianSoto ได้รับการแก้ไข
Šime Vidas

1
เวลาต่ำสุดsetTimeoutคือ 4 มิลลิวินาทีโดยสเป็ค HTML5 การให้ 0 จะยังคงใช้เวลาน้อยที่สุด แต่ใช่มันทำงานได้ดีเหมือนฟังก์ชั่นเลื่อนเวลา
hegez

30

ที่นี่คุณมีวิธีแก้ปัญหาอย่างง่าย (อื่น ๆ เขียนเกี่ยวกับมัน) http://www.benlesh.com/2012/05/calling-javascript-function.html

และที่นี่คุณมีวิธีการแก้ปัญหาพร้อม:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

ทดสอบ 1 ( อาจส่งออก '1 x 2 3' หรือ '1 2 x 3' หรือ '1 2 3 x' ):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

ทดสอบ 2 ( จะส่งออก 'x 1' เสมอ ):

async(function() {console.log('x');}, function() {console.log(1);});

ฟังก์ชั่นนี้จะถูกดำเนินการกับการหมดเวลา 0 - มันจะจำลองงานแบบอะซิงโครนัส


6
TEST 1 สามารถส่งออกได้จริงเท่านั้น '1 2 3 x' และ TEST 2 รับประกันว่าจะส่งออก '1 x' ทุกครั้ง เหตุผลเพื่อให้ได้ผลลัพธ์ที่ไม่คาดคิดในการทดสอบ 2 เป็นเพราะconsole.log(1)มีการเรียกและเอาท์พุท ( undefined) async()จะถูกส่งเป็นข้อโต้แย้งที่ ในกรณีของการทดสอบ 1 ฉันคิดว่าคุณไม่เข้าใจคิวการดำเนินการของ JavaScript อย่างสมบูรณ์ เนื่องจากการเรียกแต่ละครั้งconsole.log()เกิดขึ้นในสแต็กเดียวกันxรับประกันว่าจะถูกบันทึกครั้งสุดท้าย ฉันลงคะแนนคำตอบนี้เพื่อข้อมูลที่ผิด แต่มีตัวแทนไม่เพียงพอ
Joshua Piccari

1
@Joshua: ดูเหมือน @fider หมายถึงการเขียนการทดสอบ async(function() {console.log('x')}, function(){console.log(1)});2:
nzn

ใช่ @ nzn และ @Joshua ฉันหมายถึงTEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- ฉันแก้ไขแล้ว
fider

เอาต์พุต TEST 2 คือ 1 x ใน async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen

10

นี่คือฟังก์ชันที่ใช้ในฟังก์ชันอื่นและส่งออกเวอร์ชันที่รัน async

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

มันถูกใช้เป็นวิธีง่ายๆในการสร้างฟังก์ชั่น async:

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

สิ่งนี้แตกต่างจากคำตอบของ @ fider เนื่องจากฟังก์ชั่นนั้นมีโครงสร้างของตัวเอง (ไม่มีการโทรกลับเพิ่มในฟังก์ชั่นที่มีอยู่แล้ว) และเพราะมันสร้างฟังก์ชั่นใหม่ที่สามารถใช้งานได้


setTimeout ไม่สามารถนำมาใช้ในวง(เรียกใช้ฟังก์ชันเดียวกันหลายต่อหลายครั้งที่มีการขัดแย้งที่แตกต่างกัน)
2284570

@ user2284570 นั่นคือสิ่งที่ปิดเพื่อ (function(a){ asyncFunction(a); })(a)
หมุน

1
IIRC คุณยังสามารถบรรลุเป้าหมายนี้ได้โดยไม่ต้องปิด:setTimeout(asyncFunction, 0, a);
หมุน

1
หาก async เราหมายถึง: ทำงานในพื้นหลังขนานกับเธรดหลักดังนั้นนี่ไม่ใช่ async อย่างแท้จริง ทั้งหมดนี้จะทำคือชะลอการดำเนินการกับ process.nextTick สิ่งที่รหัสคุณมีในฟังก์ชั่นจะถูกดำเนินการในหัวข้อหลัก หากฟังก์ชั่นได้รับการตั้งค่าให้คำนวณ PI จากนั้นแอปจะหยุดทำงานโดยมีหรือไม่มีการหมดเวลา!
Mike M

1
ฉันไม่เข้าใจว่าทำไมคำตอบนี้จึงถูกยกระดับขึ้น เมื่อฉันใส่สิ่งนี้ลงในโค้ดโปรแกรมจะบล็อกจนกว่าจะเสร็จสิ้นการทำงานซึ่งเป็นสิ่งที่ไม่ควรทำ
Martin Argerami

6

แก้ไข:ฉันเข้าใจผิดโดยสิ้นเชิงคำถาม setTimeoutในเบราว์เซอร์ผมจะใช้ ถ้ามันเป็นสิ่งสำคัญที่จะวิ่งเข้าไปในหัวข้ออื่นผมจะใช้เว็บแรงงาน


1
? นี่ไม่ได้สร้างฟังก์ชั่น async: O
markzzz

6

ล่าช้า แต่เพื่อแสดงวิธีแก้ปัญหาอย่างง่ายโดยใช้promisesหลังจากการแนะนำในES6จะจัดการการโทรแบบอะซิงโครนัสได้ง่ายขึ้นมาก:

คุณตั้งค่ารหัสแบบอะซิงโครนัสในสัญญาใหม่:

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

หมายเหตุเพื่อตั้งค่าresolve()เมื่อการโทร async เสร็จสิ้น
จากนั้นคุณเพิ่มรหัสที่คุณต้องการเรียกใช้หลังจากการโทร async เสร็จสิ้นภายใน.then()สัญญา:

asyncFunct.then((result) => {
    console.log("Exit");    
});

นี่คือตัวอย่างของมัน:

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");            	
            resolve();
        }); 			
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

หรือJSFiddle


6

หน้านี้จะแนะนำคุณเกี่ยวกับพื้นฐานของการสร้างฟังก์ชั่น async javascript

ตั้งแต่ ES2017 ฟังก์ชัน javacript แบบอะซิงโครนัสนั้นเขียนได้ง่ายกว่ามาก นอกจากนี้คุณควรอ่านเพิ่มเติมเกี่ยวกับสัญญา


ลิงก์ตาย
Martin Argerami

3

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

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

คุณสามารถใช้สิ่งนี้:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);

2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

แม้ว่าจะไม่ได้แตกต่างจากโซลูชั่นอื่น ๆ อย่างเป็นพื้นฐาน แต่ฉันคิดว่าโซลูชันของฉันทำสิ่งที่ดีเพิ่มเติม:

  • มันช่วยให้พารามิเตอร์กับฟังก์ชั่น
  • มันผ่านการส่งออกของฟังก์ชั่นการโทรกลับ
  • มันถูกเพิ่มเพื่อFunction.prototypeให้วิธีที่ดีกว่าในการเรียก

นอกจากนี้ความคล้ายคลึงกันของฟังก์ชั่นในตัวก็Function.prototype.applyเหมาะสมสำหรับฉันเช่นกัน


1

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

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

ดังนั้นฟังก์ชั่นนี้ช่วยปกป้องจากข้อผิดพลาดและส่งมอบให้กับการเรียกกลับเพื่อจัดการอย่างสง่างาม แต่รหัสนั้นเป็นซิงโครนัสเหมือนกับฟังก์ชัน JS อื่น ๆ


1

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

รหัสด้านล่างมีฟังก์ชั่น async การโทรเป็นพื้นหลัง

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

นี่คือตัวอย่างสำหรับการใช้งาน:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

นี่เป็นการเรียกใช้ฟังก์ชันที่วนซ้ำforด้วยจำนวนมากเพื่อใช้เวลาในการสาธิต asynchronicity จากนั้นจะได้รับสองเท่าของจำนวนที่ส่งผ่าน asyncวิธีการให้functionซึ่งเรียกฟังก์ชั่นที่ต้องการในพื้นหลังและในที่ที่มีให้เป็นพารามิเตอร์ของasyncการเรียกกลับreturnในพารามิเตอร์ที่เป็นเอกลักษณ์ ดังนั้นในฟังก์ชั่นการโทรกลับฉันalertผลลัพธ์


0

MDN มีตัวอย่างที่ดีเกี่ยวกับการใช้ setTimeout เพื่อรักษา "this"

ชอบดังต่อไปนี้:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.