当JavaScript是单线程语言时,如何在JavaScript中具有异步无阻塞代码?

时间:2019-04-01 21:37:35

标签: javascript asynchronous async-await es6-promise

我是JavaScript的新手,我习惯于通过创建工作线程来在Java中创建异步无阻塞代码。

鉴于 JavaScript是单线程语言,我不理解异步无阻塞代码在JavaScript中如何工作。

例如,JavaScript中的承诺回调

两者都不阻塞,并且允许主线程逐行继续执行程序的其余部分,并且仅当在稍后的时间(例如数据准备就绪)实现了promise时,promise.resolve()或执行回调。

现在,我很难理解哪个线程准确地跟踪了何时实现/准备好诺言,或者如果主线程已经继续执行并忙于执行其他操作,则随时可以执行回调?

我的逻辑告诉我,作为Java程序员,必须有一个后台工作线程负责在准备好执行回调/承诺时通知主线程,这与JavaScript是单线程这一事实相矛盾,因此我一定是错的。

我希望对此概念做一个很好的解释。 预先感谢!

2 个答案:

答案 0 :(得分:3)

有两种方法可以使用计算机实现并发行为:

  1. 您同时运行两台计算机(或处理器)(多线程)。

  2. 您可以在不同的任务之间快速切换,从而看起来好像它们可以同时运行(多任务)。

现在,尽管Java经常在这些任务之间切换,但是JavaScript仅在当前任务完成时才切换任务;因此,该代码不会同时运行,并且表现为“单线程”。

以“单线程方式”执行JavaScript并不意味着底层引擎是单线程的。事实并非如此,它使用一些线程来管理I / O。每当您启动异步操作时,引擎就会将其委派给一个后台线程。然后,当任务完成时,另一个线程将结果返回到主线程,然后该主线程将调回JavaScript代码。

您可以将JavaScript引擎视为TaskExecutor中启动线程的ThreadPool。但是,您只能控制TaskExecutor

答案 1 :(得分:1)

承诺 / 回调阻止。您可以运行以下示例:

let shiftIsPressed = false;

function setEventsCopyable(isCopyable) {
  shiftIsPressed = !shiftIsPressed;
  calendar.setOption("droppable", isCopyable);
  calendar.setOption("editable", !isCopyable);
}

document.addEventListener("keydown", event => {
  if (event.keyCode === 16 && !shiftIsPressed) {
    setEventsCopyable(true);
  }
});

document.addEventListener("keyup", event => {
  if (shiftIsPressed) {
    setEventsCopyable(false);
  }
});

let containerEl = document.getElementById("calendar");
let calendarEl = document.getElementById("calendar");

new Draggable(containerEl, {
  itemSelector: ".fc-event",
  eventData: function(eventEl) {
    return {
      title: eventEl.innerText
    };
  }
});

var calendar = new Calendar(calendarEl, {
  plugins: ["dayGrid", "interaction"],
  defaultView: "dayGridMonth",
  // Determines whether the events on the calendar can be modified.
  editable: true,
  // Determines if external draggable elements can be dropped onto the calendar.
  dropAccept(el) {
    return shiftIsPressed;
  },
  events: [
    {
      title: "Test 1",
      start: "2019-04-01"
    },
    {
      title: "Test 2",
      start: "2019-04-03",
      end: "2019-04-05"
    },
    {
      title: "Test 3",
      start: "2019-04-22",
      end: "2019-04-25"
    },
    {
      title: "Test 4",
      start: "2019-04-19"
    }
  ]
});

calendar.render();

您将看到function func1() { return new Promise(resolve => { setTimeout(() => { console.log('func1'); while (true) {} resolve(); }, 0); }); } function func2() { func1(); func1(); console.log('func2'); } func2(); 仅被打印一次,因为执行的第一个func1()的Promise阻止了第二个函数的调用。

您可能会考虑将每个代码块都放在(事件循环的)队列中。

对于异步调用,代码的异步部分被排队,等待事件循环运行。当然,事件循环本身就是JavaScript应用程序中的单线程。

例如,如果将while循环移动到func1的末尾,您将永远不会看到func2()被打印出来,因为函数func2()阻塞了队列,因此Promise将永不解决。

用作调度程序的“线程”应该是应用程序之外的另一个线程,但是由于它不是应用程序的一部分(由JavaScript开发人员/用户编写),因此我认为JavaScript并非“单线程”语言”。