前端异步系列(一)-理解什么是同步异步

381 阅读3分钟

1. 异步编程

摘录自" JavaScript 红宝书":理解什么是异步

同步行为和异步行为的对立统一是计算机科学的一个基本概念。特别是在 JavaScript 这种单线程事件循环模型中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。如果在等待其他操作完成的同时,即使运行其他指令,系统也能保持稳定,那么这样做就是务实的。

重要的是,异步操作并不一定计算量大或要等很长时间。只要你不想为等待某个异步操作而阻塞线程执行,那么任何时候都可以使用。

2. 代码视角理解同步异步

同步行为对应内存中顺序执行的处理器指令;每条指令都会严格按照它们出现的顺序来执行。

2.1 同步代码

同步代码执行过程。若遇到alert、报错会卡住,不会往下执行同步代码

console.log('输出顺序1', 111)
alert(222)      // alert会卡住,点击确认之后才会往下执行
console.log('输出顺序2', 333)

手动抛出错误,同步代码遇到报错,会卡住不再往下继续执行代码

console.log(111)
throw new Error()
console.log(222) // 不执行

2.2 异步代码

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。

解决js单线程等待问题:同步代码执行时,若遇到异步代码会先跳过,继续执行完所有的同步代码,在回来按照异步栈的顺序来执行异步

从setTimeout理解异步

console.log('输出顺序1', 111)
setTimeout(() => {
    alert(333)     // alert不会卡住,输出完333才会执行这个alert
}, 3000)
console.log('输出顺序2', 222)

从Promise理解异步

const p = Promise.resolve(222)
p.then(res => {
  console.log('输出顺序2', res)
})
console.log('输出顺序1', 111)

3. 哪些程序属于异步程序

  1. 浏览器级定义的API(宏任务): setTimeout、setInterval、网络请求(new XMLHttpRequest())
  2. ES6定义的API(微任务): Promise的then()/catch()/finally()、async/await(属于promise的语法糖)
  3. 相同类型的异步执行顺序和推入调用栈的先后顺序有关,先进入先执行;不同类型的异步执行顺序“微任务 > 宏任务”(为什么这样执行请看后续系列文章)

4. 异步回调

串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数(俗称“回调地狱”)来解决。

传统的异步回调地狱问题:这种写法就很不优雅,且函数作为参数传递理解起来也比较困难

function fetchData(callback) {
  setTimeout(() => {
    console.log("Data fetched successfully.");   // 1s后输出
    callback();
  }, 1000);
}

function processUserData(userData, callback) {
  setTimeout(() => {
    console.log(`Processing user data: ${userData}`);   // 2s后输出
    callback();
  }, 1000);
}

function displayResult(result) {
  console.log(`Result displayed: ${result}`);   // 2s后输出
}

// 使用传统异步回调地狱
fetchData(() => {
  processUserData("John", () => {
    displayResult("Done");
  });
});

使用Promise链式调用解决异步回掉地狱,并实现其功能:这种写法逻辑清晰,比较容易理解

function packagingPromise(val1 = '', val2 = null){
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({val1, val2})
      }, 1000);
    })
}

packagingPromise("Data fetched successfully.")
    .then(res => {
      console.log(res.val1)     // 1s后输出
      return packagingPromise("Processing user data:", 'John')
    })
    .then(res => {
      console.log(`${res.val1}${res.val2}`)     // 2s后输出
      return 'Done'
    })
    .then(res => {
      console.log(res)    // 2s后输出
    })