javascript异步是什么_回调函数和promise有何区别?

JavaScript异步解决单线程阻塞导致页面卡死的问题,通过事件循环让耗时操作不占用主线程,保障UI流畅响应;它不提速但防停滞,核心是“别停”而非“快”。

JavaScript异步到底在解决什么问题?

JavaScript 是单线程的,主线程一旦被阻塞,页面就卡住、无法响应点击或滚动。所谓“异步”,就是让耗时操作(比如 fetch()setTimeout()fs.readFile())不占着主线程等结果,而是先让出控制权,等它做完再通知你——这样页面才不卡。

键不是“慢”,而是“别停”。异步机制本身不提速,但能保流畅。

  • 常见错误现象:写了个 while (true) 或同步读大文件,整个页面冻结,连开发者工具都打不开
  • 真实场景:用户点按钮后发请求,你不该让按钮一直“按下去”状态不动,而应立刻反馈“加载中”,再等数据回来更新 UI
  • 注意:异步 ≠ 多线程。JS 仍只有一条主线程,靠浏览器/Node.js 的事件循环(Event Loop)调度回调和 Promise 微任务

回调函数为什么容易失控?

回调函数是“你告诉我做完后干啥”,把处理逻辑直接塞进参数里。它简单、兼容性好,但一串起来就暴露三个硬伤:

getUser(id, function(user) {
  getPosts(user.id, function(posts) {
    getComments(posts[0].id, function(comments) {
      console.log(comments);
    });
  });
});
  • 嵌套即地狱:每多一层异步,缩进+1,逻辑向右滑出屏幕,改一处要数括号
  • 错误处理分散:每个回调都得写 if (err) { ... },漏一个就静默失败
  • 无法用 try/catch:异步回调执行时已脱离原始调用栈,try/catch 捕不到
  • 控制流难复用:想“并发发起 3 个请求,全部成功才继续”,得手动计数 + 判断,极易出错

Promise 是怎么把异步“状态化”的?

Promise 不是回调的语法糖,而是把异步操作包装成一个有明确状态的对象:pendingfulfilledrejected,且状态不可逆。你不再告诉它“做完干啥”,而是问它“结果是什么”,然后声明式地响应。

getUser(id)
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(err => console.error('出错了:', err));
  • 链式调用扁平化:每个 .then() 返回新 Promise,天然避免嵌套
  • 错误自动冒泡:任意 .then() 中抛错、或返回被 reject 的 Promise,都会被后续最近的 .catch() 捕获
  • 组合能力开箱即用Promise.all([p1, p2]) 等全部完成;Promise.race([p1, p2]) 取最快那个;Promise.allSettled() 管它成不成,全等完再说
  • 注意:Promise 本身不可取消、没有进度回调、pending 状态无法感知是否卡住——这些是它没解决、也不该解决的问题

什么时候该用回调,什么时候必须转 Promise?

不是谁淘汰谁,而是看接口契约和维护成本:

  • 老 API(如 Node.js 的 fs.readFile(path, callback))仍只接受回调 → 用 util.promisify() 包一层,或手写 new Promise(...) 封装,别裸写回调链
  • 事件监听(button.addEventListener('click', handler))本质是事件机制,不是“异步操作问题”,继续用回调更自然
  • 新项目、封装层、业务逻辑中,一律返回 Promise —— 即使底层用回调,对外也该提供 .then() 接口
  • 绝对避免混合:一个函数既接收 callback 参数,又返回 Promise,调用方会懵,资源可能泄漏

最常被忽略的一点:Promise 构造器里的执行器函数(new Promise((resolve, reject) => {...}))是**同步立即执行**的,里面写错 throw 会直接崩,不是异步错误——这点和 .then() 里的 throw 行为完全不同。