Làm cách nào để ngăn Node.js thoát ra trong khi chờ gọi lại?


92

Tôi có mã như thế này:

var client = new mysql.Client(options);
console.log('Icanhasclient');

client.connect(function (err) {
  console.log('jannn');
  active_db = client;
  console.log(err);
  console.log('hest');

  if (callback) {
    if (err) {
      callback(err, null);
    }

    callback(null, active_db);
  }
});

Vấn đề của tôi là Node kết thúc ngay lập tức khi tôi chạy nó. Nó in ra 'Icanhasclient', nhưng không có lệnh nào của console.log bên trong callback được gọi.

(mysql trong ví dụ này là node-mysql .

Có điều gì đó có thể được thực hiện để khiến node.js đợi lệnh gọi lại hoàn tất trước khi thoát không?


1
ý bạn là gì ngăn lối ra? Nodejs không thoát cho đến khi hoàn thành tất cả các lệnh gọi lại. Đó là một quá trình đơn lẻ.
— neebz

4
@nEEbz: Nếu đúng như vậy, tại sao tập lệnh của tôi thoát ra mà không thực hiện lệnh gọi lại?
— mikl

Đó là một số vấn đề với việc kết nối với cơ sở dữ liệu của bạn; Tôi không chắc tại sao nó không kích hoạt cuộc gọi lại. Nó có đưa ra bất kỳ lỗi nào không?
— neebz

Không, nó chỉ thất bại một cách âm thầm :(
— mikl

Điều đó rất kỳ lạ. Tôi hy vọng chúng tôi tìm thấy một câu trả lời cho điều này.
— neebz

Câu trả lời:


49

Gọi lại không được xếp hàng

Nút chạy cho đến khi tất cả các hàng đợi sự kiện trống. Một cuộc gọi lại được thêm vào hàng đợi sự kiện khi một cuộc gọi chẳng hạn như

  emmiter1.on('this_event',callback).

đã thực hiện. Lệnh gọi này là một phần của mã do nhà phát triển mô-đun viết.

Nếu một mô-đun là một cổng nhanh từ phiên bản đồng bộ / chặn, điều này có thể không xảy ra cho đến khi một số phần của hoạt động đã hoàn thành và tất cả các hàng đợi có thể trống trước khi điều đó xảy ra, cho phép nút thoát ra một cách im lặng.

Đây là một lỗi lén lút, một lỗi mà nhà phát triển mô-đun có thể không gặp phải trong quá trình phát triển, vì nó sẽ ít xảy ra hơn trong các hệ thống bận rộn với nhiều hàng đợi vì hiếm khi tất cả chúng đều trống vào thời điểm quan trọng.

Một công cụ phát hiện lỗi / sửa lỗi khả thi cho người dùng là chèn một sự kiện hẹn giờ đặc biệt trước khi gọi hàm nghi ngờ.


"điều này có thể không xảy ra cho đến khi một số phần của hoạt động đã hoàn thành" - Bạn có thể làm rõ điều này? Bạn đang nói rằng nút có thể thoát ra trong khi một đoạn mã vẫn đang chạy trước khi nó có cơ hội thêm lệnh gọi lại vào hàng đợi sự kiện?
— BT

Tôi đoán ý của anh ấy là nếu thư viện đã chuyển đổi mã đã từng là đồng bộ thành phiên bản không đồng bộ mới, có thể người tạo thư viện đã quên xếp hàng các lệnh gọi lại và hàng đợi sự kiện bị trống ở đâu đó giữa quá trình thực thi của bạn, do đó kết thúc với không được báo gọi lại.
— Dielson Bán hàng vào

1
Có cách nào dễ dàng để tái tạo vấn đề này một cách đáng tin cậy không?
— Jordan Brough

Cảm ơn vì điều đó! Tôi đã có một quá trình "treo" và không hiểu tại sao nó không thoát. Sau khi làm sạch tất cả các trình nghe / gọi lại sự kiện của tôi, quá trình sẽ thoát.
— ps2goat

7
CẢNH BÁO - Mặc dù có rất nhiều phiếu tán thành, tôi nghĩ rằng thông tin trong câu trả lời này không chính xác. Gọi EventEmitter.on()không không thêm bất cứ điều gì vào vòng lặp sự kiện rằng Node sẽ đợi (ít nhất là trong phiên bản hiện tại của Node). Có vẻ như chỉ những việc như đặt thời gian chờ và thực hiện cuộc gọi không đồng bộ từ thư viện lõi mới có thể làm được điều này. Bạn có thể kiểm tra điều này dễ dàng bằng cách viết một chương trình một dòng mong đợi một sự kiện sẽ không bao giờ xảy ra. Từ câu trả lời này, bạn mong đợi nó sẽ không bao giờ kết thúc, nhưng nó sẽ chấm dứt ngay lập tức. Xem câu trả lời này để biết thêm thông tin.
— davnicwil 19/07/19

38

Bạn chỉ có thể đưa ra setTimeout hoặc thời gian chờ định kỳ với setInterval.

Nếu bạn muốn kiểm tra các điều kiện thoát, bạn cũng có thể thực hiện thời gian chờ có điều kiện:

(function wait () {
   if (!SOME_EXIT_CONDITION) setTimeout(wait, 1000);
})();

Đặt cái này vào cuối mã của bạn và bảng điều khiển sẽ chỉ đợi ... và đợi ... cho đến khi bạn muốn nó đóng.


4
Đó là một cách giải quyết tốt, nhưng không phải là một giải pháp tốt (không phải lỗi của bạn mà là của nút).
— peterh - Phục hồi Monica

13

Giải pháp của tôi là khởi tạo EventEmitter và lắng nghe sự kiện tùy chỉnh của tôi.

var eventEmitter = new process.EventEmitter();

thì tôi đã gọi eventEmitter.emittừ callback async:

client.connect(function (err) {
    eventEmitter.emit('myevent', {something: "Bla"})
});

Điều cuối cùng trong kịch bản của tôi là eventEmitter.on:

eventEmitter.on('myevent', function(myResult){
  // I needed the result to be written to stdout so that the calling process could get it
  process.stdout.write(JSON.stringify(myResult));
});

Sau đó, Node sẽ đợi cho đến khi trình xử lý sự kiện chạy xong.


Tôi nhận được lỗi `process.EventEmitter không phải là một phương thức khởi tạo`. Bạn đề nghị điều gì?
— Anthony Kong

Tra cứu cách tạo EventEmitter. coligo.io/nodejs-event-emitter có vẻ như là một nguồn tốt. Có thể các phiên bản khác nhau của Node đã thay đổi cách tạo.
— you786,

2
EventEmitterlớp được định nghĩa bởi eventsmô-đun. Vì vậy, để tạo ra một phát mới: const EventEmitter = require('events'); var eventEmitter = new EventEmitter().
— CedX

1
CẢNH BÁO - xem nhận xét của tôi về câu trả lời được chấp nhận. Tôi nghĩ điều này là sai và ít nhất là không hoạt động đối với phiên bản hiện tại của nút.
— davnicwil 19/07/19

3

Dựa trên câu trả lời của @ Todd, tôi đã tạo một lớp lót. Đưa nó vào đầu tập lệnh của bạn và đặt done = truekhi bạn hoàn thành:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

Thí dụ:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

someAsyncOperation().then(() => {
  console.log('Good to go!');
  done = true;
});

Làm thế nào nó hoạt động? Nếu chúng ta mở rộng nó một chút:

// Initialize the variable `done` to `undefined`
// Create the function wait, which is available inside itself
// Note: `var` is hoisted but `let` is not so we need to use `var`
var done = (function wait () {

  // As long as it's nor marked as done, create a new event+queue
  if (!done) setTimeout(wait, 1000);

  // No return value; done will resolve to false (undefined)
})();

Tại sao bạn cài đặt xong cho đầu ra của IIFE, chỉ đơn giản là false sẽ tốt hơn với IIFE sau đó.
— tiffon

1
@tiffon Có lẽ chỉ để làm cho nó một lớp lót.
— Pedro A

0

Đây là hai xu của tôi:

async function main()
{
    await new Promise(function () {});
    console.log('This text will never be printed');
}

function panic(error)
{
    console.error(error);
    process.exit(1);
}

// https://stackoverflow.com/a/46916601/1478566
main().catch(panic).finally(clearInterval.bind(null, setInterval(a=>a, 1E9)));

0

Đây là cách tôi làm điều đó. Tôi đặt điểm nhập chính của mình trả về một lời hứa, sau đó tôi sử dụng trình bao bọc nhỏ này để đảm bảo rằng miễn là lời hứa đó không được giải quyết, nút sẽ không thoát:

function wrapPromiseMain(entryPoint) {
    const pollTime = 1000000;
    let interval = setInterval(() => {}, pollTime);
    
    return entryPoint().finally(() => {clearInterval(interval)});
}

Để sử dụng nó, chỉ cần hứa-ize điểm nhập chính của bạn và chuyển nó làm đối số cho trình bao bọc:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

Tôi thấy nó gọn gàng hơn một chút so với các vòng thăm dò cơ bản vì nó sẽ tự động hủy bộ hẹn giờ khi lời hứa kết thúc, vì vậy nó không thêm bất kỳ độ trễ bổ sung nào. Do đó, thời gian bỏ phiếu về cơ bản có thể là mãi mãi nếu bạn muốn.


Lưu ý: đã chỉnh sửa sau khi đăng vì tôi nhận ra rằng tôi có thể làm cho nó ngắn gọn hơn nữa bằng cách sử dụng setInterval thay vì các lệnh gọi chuỗi đến setTimeout.
— ad1c371f7d05

-1

Tôi đã xem xét thư viện felixge / node-mysql và không thấy tham chiếu đến lệnh client.connect trong API. Đây có phải là cuộc gọi thực sự mà bạn đang cố gắng thực hiện (không cố tỏ ra khó hiểu ở đây)? Bất kể IMHO, bạn cần phải suy nghĩ thêm về cách Javascript được thiết kế, bởi vì nó sử dụng một mô hình lập trình khác với hầu hết các ngôn ngữ phổ biến khác.

Vấn đề đầu tiên tôi thấy trong mã của bạn là bạn chưa xác định lệnh gọi lại, vì vậy nó không thực sự tồn tại. Tôi giả sử console.log (callback) là không xác định. Từ mã của bạn, hàm ẩn danh là 'gọi lại' cho hàm client.connect. Bạn phải xác định những gì bạn đang gọi là 'gọi lại' ở phạm vi cao hơn. Ví dụ, tôi sẽ định nghĩa một hàm myCallback tồn tại trong phạm vi cao hơn hàm ẩn danh của client.connect. Có thể hữu ích khi tra cứu phạm vi biến Javacscript .

    var myCallback(err, response) {
      if (err) {
        console.log('err:%s',err);
      } else {
        console.log('response:%s',response);
      }
    }

    client.connect(err, function(response) {
      // this anonymous function is the callback to client.connect, the var
      // 'callback' would be undefined.
      if (err) {
        myCallback(err);
        return; // Explicit call to return, else the lines below would run.
      } 
      myCallback(null, response);
    });

Thứ hai, nếu bạn không gọi hàm return một cách rõ ràng trong Javascript, thì hàm sẽ tiếp tục xử lý. Tôi đã bị cắn bởi chính mình . Cuối cùng, Javascript chạy một vòng lặp hướng sự kiện có nghĩa là nó sẽ không bao giờ đợi các hàm trả về giá trị, đó là lý do tại sao chúng ta có tất cả các lệnh gọi lại này ngay từ đầu. Bạn có thể buộc Javascript hoạt động khác, ví dụ bằng cách sử dụng vòng lặp while cho đến khi một điều kiện là đúng. Xem thư viện 'async' theo caolan , để biết các chiến lược khác nhau về thao tác vòng lặp sự kiện. Bất lợi lớn khi lạm dụng các phương pháp này là bạn thực sự lãng phí chu kỳ / chặn CPU khi bạn có thể nên sử dụng nhiều lệnh gọi lại hơn và chỉ đơn giản là nghĩ lại cách chương trình của bạn hoạt động.


-2

Hãy thử cái này. Kiểm tra nếu điều này giúp đỡ.

var client = new mysql.Client(options);
console.log('Icanhasclient');
var verbose;

if (!verbose) {
    return new Promise(function (resolve, reject) {
        client.connect(function (err) {
            if (err) {
                console.log(Error in connecting
                SQL ${err}
            )
                ;
                return reject(err);
            }
            verbose = client;
            return resolve(verbose);
        })
    })
} else {
    return new Promise(function (resolve) {
        resolve(verbose);
    })
}
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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.