一个帮助理解javascript单线程异步非阻塞的例子

275 阅读2分钟

目录结构

├── server.js
├── index.html

分别保存如下代码到对应文件,运行

node server.js

html使用fetch作为请求库,请使用新版浏览器
浏览器打开http://127.0.0.1:3000点击对应按钮请求数据,观察页面响应状态和控制台输出,也可以用此来模拟并发场景下,阻塞和非阻塞对其他请求的影响.界面如图:

服务端建立了三个接口:

  • 访问 /async 接口时执行非阻塞程序
  • 访问 /sync 接口时执行阻塞程序
  • 访问 /other 接口时执行没有延时的普通处理程序

请求普通接口时,程序会立即响应数据;
请求非阻塞接口时会请求返回promise的处理程序,将一个定时器放入事件队列,等10秒后定时器结束后将promise决议,程序响应http请求,如果在此期间请求普通接口,由于cpu并没有被阻塞住,所以可以很快响应数据;
请求阻塞接口时控制台开始疯狂打印日志,由于console.log是同步且cpu密集型任务,会将cpu一直占用,此时请求普通接口将一直被阻塞住,直到log结束后cpu才有空闲处理其他请求;

服务端代码server.js

const http = require('http');
const fs = require('fs');
const util = require('util');
const readFileAsync = util.promisify(fs.readFile)

// 启动一个web服务器
const server = http.createServer(async (req, res) => {
  let data
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  switch (req.url) {
    case '/async':
      await asyncOperation(10000)
      data = "async"
      break;
    case '/sync':
      await syncOperation(100000)
      data = "sync"
      break;
    case '/other':
      console.log('other');
      data = "other"
      break;
  
    default:
      res.writeHead(200, { 'Content-Type': 'text/html' });
      data = await readFileAsync('./index.html')
      break;
  }
  res.end(data);
});

server.listen(3000)

/**
 * 异步返回
 * @param int timeout 
 */
function asyncOperation(timeout) {
  let i = 0
  let interval = setInterval(() => {
    console.log(i++);
  }, 1000);
  return new Promise(resolve => {
    setTimeout(() => {
      clearInterval(interval)
      resolve()
    }, timeout);
  })
}

/**
 * console.log是同步的阻塞处理程序
 * @param int times 
 */
function syncOperation(times) {
  for (let index = 0; index < times; index++) {
    console.log(index)
  }
}

页面代码index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/vue@next"></script>
  <title>同步异步</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    html,
    body {
      height: 100%;
    }

    #app {
      width: 100%;
      min-height: 100%;
      overflow-y: auto;
      display: flex;
    }

    .box {
      flex: 1;
      text-align: center;
      border-right: solid 1px #000;
    }

    .box:last-child {
      border: none;
    }

    button {
      padding: 5px 10px;
      cursor: pointer;
    }

    .responseList {
      margin-top: 5px;
      border-top: 1px solid #000;
      overflow-y: auto;
    }

    li {
      list-style: none;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="box">
      <h1>阻塞接口</h1>
      <button @click='sendRequest("sync")'>发送请求</button>
      <div class="responseList">
        <li v-for="(item, i) in data.syncRspList" :key="i">
          {{ data.syncRspList.length - i }} : {{ item }}
        </li>
      </div>
    </div>
    <div class="box">
      <h1>非阻塞接口</h1>
      <button @click='sendRequest("async")'>发送请求</button>
      <div class="responseList">
        <li v-for="(item, i) in data.asyncRspList" :key="i">
          {{ data.asyncRspList.length - i }} : {{ item }}
        </li>
      </div>
    </div>

    <div class="box">
      <h1>普通接口</h1>
      <button @click='sendRequest("other")'>发送请求</button>
      <div class="responseList">
        <li v-for="(item, i) in data.otherRspList" :key="i">
          {{ data.otherRspList.length - i }} : {{ item }}
        </li>
      </div>
    </div>
  </div>
  <script>
    let reqUrl = {
      sync: {
        listObj: "syncRspList",
        url: "/sync"
      },
      async: {
        listObj: "asyncRspList",
        url: "/async"
      },
      other: {
        listObj: "otherRspList",
        url: "/other"
      },
    }
    const vueConfig = {
      setup() {
        const data = Vue.reactive({
          syncRspList: [],
          asyncRspList: [],
          otherRspList: [],
        })
        async function sendRequest(type) {
          const { url, listObj } = reqUrl[type]
          const arr = data[listObj]
          let arrLength = arr.unshift("等待响应...")
          data[listObj]
          let res
          try {
            res = await fetch(url)
            res = await res.text()
          }
          catch (e) {
            res = "请求超时!"
          }
          finally {
            arr.splice(-arrLength, 1, res)
          }
        }
        return {
          data,
          sendRequest,
        }
      }
    }
    Vue.createApp(vueConfig).mount('#app')
  </script>
</body>

</html>