Làm cách nào để tạo chức năng Không đồng bộ trong Javascript?


143

Kiểm tra này :

<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");    
});

Như bạn có thể thấy trong bảng điều khiển, chức năng "animate" không đồng bộ và nó "rẽ nhánh" là dòng chảy của mã khối xử lý sự kiện. Trong thực tế :

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

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

theo dòng chảy của mã khối!

Nếu tôi muốn tạo function asyncFunct() { }hành vi của mình với hành vi này, làm thế nào tôi có thể làm điều đó với javascript / jquery? Tôi nghĩ rằng đó là một chiến lược mà không cần dùng setTimeout()


hãy xem các nguồn jQuery :)
yatskevich

Mathod .animate () sử dụng hàm gọi lại. Animate sẽ gọi lại khi hoạt ảnh hoàn tất. Nếu bạn cần cùng một hành vi của .animate () thì cái bạn cần là một cuộc gọi lại (được gọi bởi hàm "chính" sau một số thao tác khác). Sẽ khác nếu bạn cần một hàm async "đầy đủ" (một hàm được gọi là ngăn chặn luồng thực thi). Trong trường hợp này, bạn có thể sử dụng setTimeout () với độ trễ gần 0.
Fabio Buda

@Fabio Buda: tại sao gọi lại () nên thực hiện một loại không đồng bộ? Trên thực tế, nó không jsfiddle.net/5H9XT/9
markzzz

thực tế sau khi "gọi lại" tôi đã trích dẫn một phương thức async "đầy đủ" với setTimeout. Ý tôi là gọi lại dưới dạng giả đồng bộ theo cách hàm được gọi sau mã khác :-)
Fabio Buda

Câu trả lời:


185

Bạn không thể thực hiện một chức năng không đồng bộ tùy chỉnh thực sự. Cuối cùng, bạn sẽ phải tận dụng một công nghệ được cung cấp nguyên bản, chẳng hạn như:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Một số API HTML5 như API tệp, API cơ sở dữ liệu web
  • Công nghệ hỗ trợ onload
  • ... nhiều người khác

Trong thực tế, đối với hoạt hình jQuery sử dụng setInterval .


1
Tôi đã thảo luận điều này với một người bạn ngày hôm qua để câu trả lời này là hoàn hảo! Tôi hiểu và có thể xác định các hàm async và sử dụng chúng trong JS đúng cách. Nhưng đơn giản là tại sao chúng ta không thể thực hiện những tùy chỉnh không rõ ràng đối với tôi. Nó giống như một hộp đen mà chúng ta biết cách làm cho nó hoạt động (sử dụng, giả sử setInterval) , nhưng chúng ta thậm chí không thể mở nó để xem nó được thực hiện như thế nào. Bạn có tình cờ có thêm thông tin về chủ đề này?
Matheus Felipe

2
@MeditusFelipe các chức năng đó có nguồn gốc từ việc triển khai công cụ javascript và điều duy nhất bạn có thể dựa vào là thông số kỹ thuật, ví dụ: bộ định thời HTML5 và tin vào bản chất hộp đen mà chúng hoạt động theo thông số kỹ thuật.
Spoike

10
@MeditusFelipe youtu.be/8aGhZQkoFbQ nói chuyện tốt nhất cho đến nay về chủ đề này ...
Andreas Niedermair

Một số ý nghĩa, đặc biệt là Node.js, hỗ trợsetImmediate
Jon Surrell

những gì về promises. Nó cho một awaitable?
Nithin Chandran

69

Bạn có thể sử dụng một bộ đếm thời gian:

setTimeout( yourFn, 0 );

(nơi yourFntham chiếu đến chức năng của bạn)

hoặc, với Lodash :

_.defer( yourFn );

Trì hoãn việc gọi funccho đến khi ngăn xếp cuộc gọi hiện tại bị xóa. Bất kỳ đối số bổ sung nào được cung cấp funckhi nó được gọi.


3
Điều này không hoạt động, chức năng javascript của tôi vẽ trong một khung vẽ tiếp tục khiến giao diện người dùng không phản hồi.
gab06

2
@ gab06 - Tôi muốn nói rằng chức năng vẽ canvas của bạn bị chặn vì lý do chính đáng của nó. Chia hành động của nó thành nhiều phần nhỏ hơn và gọi từng người trong số họ bằng một bộ đếm thời gian: bạn sẽ thấy giao diện theo cách này đáp ứng với các nhấp chuột của bạn, v.v.
Marco Faustinelli

Liên kết bị hỏng.
JulianSoto

1
@JulianSoto đã sửa
Vidas

1
Thời gian tối thiểu setTimeoutlà 4 mili giây theo thông số HTML5. Cho nó 0 vẫn sẽ mất khoảng thời gian tối thiểu đó. Nhưng vâng, nó hoạt động tốt như một chức năng trì hoãn.
bá chủ

30

Ở đây bạn có giải pháp đơn giản (viết khác về nó) http: //www.ben Meat.com/2012/05/calling-javascript-feft.html

Và ở đây bạn có giải pháp sẵn sàng ở trên:

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

KIỂM TRA 1 ( có thể xuất '1 x 2 3' hoặc '1 2 x 3' hoặc '1 2 3 x' ):

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

KIỂM TRA 2 ( sẽ luôn xuất 'x 1' ):

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

Hàm này được thực thi với thời gian chờ 0 - nó sẽ mô phỏng tác vụ không đồng bộ


6
TEST 1 thực sự chỉ có thể xuất ra '1 2 3 x' và TEST 2 được đảm bảo đầu ra '1 x' mỗi lần. Lý do cho kết quả không mong muốn trong TEST 2 là vì console.log(1)được gọi và đầu ra ( undefined) được truyền dưới dạng đối số thứ 2 async(). Trong trường hợp KIỂM TRA 1, tôi nghĩ bạn không hiểu đầy đủ hàng đợi thực thi của JavaScript. Bởi vì mỗi cuộc gọi sẽ diễn console.log()ra trong cùng một ngăn xếp, xđược đảm bảo sẽ được ghi lại sau cùng. Tôi sẽ bỏ phiếu cho câu trả lời này vì thông tin sai nhưng không có đủ đại diện.
Joshua Piccari

1
@Joshua: Có vẻ như @fider có nghĩa là viết TEST 2 là : async(function() {console.log('x')}, function(){console.log(1)});.
nzn

Có @nzn và @Joshua Ý tôi là TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- tôi đã sửa nó rồi
fider

Đầu ra TEST 2 là 1 x in async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen

10

Đây là một chức năng có chức năng khác và đưa ra một phiên bản chạy không đồng bộ.

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

Nó được sử dụng như một cách đơn giản để tạo chức năng async:

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

Điều này khác với câu trả lời của @ fider vì bản thân hàm có cấu trúc riêng (không có cuộc gọi lại được thêm vào, nó đã có trong hàm) và cũng vì nó tạo ra một chức năng mới có thể được sử dụng.


setTimeout không thể được sử dụng trong một vòng lặp (gọi cùng một chức năng nhiều lần với các đối số riêng biệt) .
dùng2284570

@ user2284570 Đó là những gì đóng cửa dành cho. (function(a){ asyncFunction(a); })(a)
Xoay

1
IIRC, bạn cũng có thể đạt được điều này mà không cần đóng cửa:setTimeout(asyncFunction, 0, a);
Xoay

1
Nếu theo async, chúng tôi muốn nói: chạy trong nền, song song với luồng chính thì đây không thực sự là async. Tất cả điều này sẽ làm là trì hoãn việc thực thi quy trình.nextTick. Bất cứ mã nào bạn có trong hàm sẽ được thực thi trên luồng chính. Nếu chức năng được đặt để tính PI thì ứng dụng sẽ đóng băng, có hoặc không có thời gian chờ!
Mike M

1
Tôi không hiểu tại sao câu trả lời này được nâng cao. Khi tôi đặt mã này vào mã của mình, chương trình sẽ chặn cho đến khi hàm hoàn thành, chính xác là những gì nó không nên làm.
Martin Argerami

6

Chỉnh sửa: Tôi hoàn toàn hiểu sai câu hỏi. Trong trình duyệt, tôi sẽ sử dụng setTimeout. Nếu điều quan trọng là nó chạy trong một luồng khác, tôi sẽ sử dụng Web Worker .


1
? Điều này không tạo ra chức năng không đồng bộ: O
markzzz

6

Tuy muộn, nhưng để hiển thị một giải pháp dễ dàng sử dụng promisessau khi giới thiệu trong ES6 , nó xử lý các cuộc gọi không đồng bộ dễ dàng hơn nhiều:

Bạn đặt mã không đồng bộ trong một lời hứa mới:

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

Lưu ý để đặt resolve()khi cuộc gọi async kết thúc.
Sau đó, bạn thêm mã mà bạn muốn chạy sau khi cuộc gọi async kết thúc bên trong .then()lời hứa:

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

Đây là một đoạn của nó:

$('#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>

hoặc JSFiddle


6

Trang này hướng dẫn bạn qua các điều cơ bản để tạo chức năng javascript không đồng bộ.

Kể từ ES2017, các hàm javacript không đồng bộ dễ viết hơn nhiều. Bạn cũng nên đọc thêm về Lời hứa .


Liên kết đã chết.
Martin Argerami

3

Nếu bạn muốn sử dụng Tham số và điều chỉnh số lượng hàm async tối đa, bạn có thể sử dụng một công cụ async đơn giản mà tôi đã xây dựng:

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);
    }
}

Bạn có thể sử dụng nó như thế này:

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);

Mặc dù về cơ bản không khác biệt so với các giải pháp khác, tôi nghĩ rằng giải pháp của tôi thực hiện thêm một số điều hay:

  • nó cho phép các tham số cho các chức năng
  • nó chuyển đầu ra của hàm cho hàm gọi lại
  • nó được thêm vào để Function.prototypecho phép một cách tốt hơn để gọi nó

Ngoài ra, sự tương tự với chức năng tích Function.prototype.applyhợp có vẻ phù hợp với tôi.


1

Bên cạnh câu trả lời tuyệt vời của @pimvdb và chỉ trong trường hợp bạn thắc mắc, async.js cũng không cung cấp các hàm thực sự không đồng bộ. Đây là một phiên bản rút gọn (rất) của phương thức chính của thư viện:

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);
    };
}

Vì vậy, hàm bảo vệ khỏi các lỗi và duyên dáng đưa nó vào hàm gọi lại để xử lý, nhưng mã này đồng bộ như bất kỳ hàm JS nào khác.


1

Thật không may, JavaScript không cung cấp chức năng không đồng bộ. Nó chỉ hoạt động trong một chủ đề duy nhất. Nhưng hầu hết các trình duyệt hiện đại cung cấp Workers , đó là các tập lệnh thứ hai được thực thi trong nền và có thể trả về kết quả. Vì vậy, tôi đã đạt được một giải pháp mà tôi nghĩ rằng nó hữu ích khi chạy không đồng bộ một chức năng, tạo ra một công nhân cho mỗi cuộc gọi không đồng bộ.

Mã dưới đây chứa chức năng async để gọi trong nền.

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);
};

Đây là một ví dụ cho việc sử dụng:

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

Điều này thực thi một hàm lặp đi lặp lại forvới một số lượng lớn để dành thời gian như là minh chứng cho sự không điển hình, và sau đó nhận được gấp đôi số đã qua. Các asyncphương pháp cung cấp một functionmà gọi hàm truy nã ở chế độ nền, và trong đó được cung cấp như tham số của asynccallbacks sự returntrong tham số độc đáo của nó. Vì vậy, trong chức năng gọi lại tôi alertkết quả.


0

MDN có một ví dụ điển hình về việc sử dụng setTimeout bảo toàn "cái này".

Giống như sau:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.