异步编程

基本概念

任务是完成某项操作要执行的一段代码,一个任务可以执行任何特定操作的代码,可能是简单的打印语句、复杂的算法处理、网络请求或 I/O 操作等。这些任务的执行时间各不相同,打印语句执行时间通常很短,而网络请求和 I/O 操作可能耗时较长。

如果所有的任务按照顺序执行,耗时较长的任务可能会阻塞线程,导致整体性能下降,进而影响用户体验。为了更高效地管理和执行这些任务,JavaScript 将要执行的任务分为同步任务和异步任务使得JavaScript 能够在单线程环境中有效地处理耗时操作,而不阻塞主线程。

  • 同步任务:这些是立即执行的任务,不需要等待任何条件或事件,会阻塞后续代码的执行。
  • 异步任务:这些任务不会立即执行,而是先挂起,被推迟到未来某个时间点执行,不会阻塞后续代码的执行。

在 ES6(ECMAScript 2015,JavaScript标准规范) 之前,JavaScript 的异步任务主要是通过如 setTimeout, setInterval来实现。

示例代码

//同步代码
console.log("Start");

//异步任务
setTimeout(() => {
    console.log("异步任务1");
}, 0);

//异步任务
setTimeout(() => {
    console.log("异步任务2");
}, 0);

setInterval(()=>{
     console.log("异步任务3");
}, 1000)

//同步代码
console.log("End");

输出结果

Start
End
异步任务1
异步任务2
异步任务31 秒后开始循环输出)
异步任务3  (再次循环输出)
异步任务3  (继续每秒输出)
...

分析执行流程

- console.log("Start");console.log("End"); 是同步代码,立即执行。
- setTimeout 的回调虽然是 0 毫秒延迟,但它们是异步任务,会等到当前同步任务执行完后再执行。
- setInterval 会每隔 1 秒重复执行其回调,每次执行都是一个新的宏任务。

事件循环

异步任务之所以能够被推迟到未来的某个时间点执行,而不阻塞主线程,主要依赖于事件循环机制任务队列来实现的。

  • 事件循环机制:是一种调度异步任务的模型。
  • 任务队列:任务队列是用来存储异步任务的容器,等待后续某个时间点,被事件循环调度。

下图展示了JavaScript执行引擎启动后,各个任务的执行流程。

在这里插入图片描述

在 ES6 之前(ECMAScript 2015,JavaScript标准规范),所有异步任务都被放在同一个任务队列中,缺乏更精细的控制,所以从ES6开始引入了宏任务和微任务对异步任务进行更精细的控制。

宏任务和微任务

从ES6开始引入了大量的新特性(包括 Promise 和 微任务),引入微任务是为了优化任务调度,减少延迟,提高性能,可以更高效地处理一些高优先级的小任务。

  • 微任务:具有高优先级的异步任务,通常用于较短时间内的异步操作,如 Promise 等操作。
  • 宏任务:具有较低优先级的异步任务,通常用于延迟执行或需要等待的操作,如 setTimeout 等操作。

这种任务划分和管理机制使得 JavaScript 能够在单线程环境中有效地处理异步任务,提升程序的响应性和用户体验。

JavaScript 执行引擎通过事件循环机制、宏任务队列、微任务队列在管理并执行异步任务,

在这里插入图片描述

解释:

  1. JavaScript 执行引擎启动后,开启一条主线程,按照代码顺序依次往下执行;
  2. 当执行到宏任务时,将宏任务添加到宏任务队列,等待后续事件循环调度,JavaScript主线程继续往下执行;
  3. 当执行到微任务时,将微任务添加到微任务队列,等待后续事件循环调度,JavaScript主线程继续往下执行,直到所有同步代码执行完毕;
  4. 执行完所有同步代码之后,启动事件循环,先依次执行完微任务队列中所有的微任务,直到微任务微空。
  5. 再执行一个宏任务后,判断微任务队列是否为空(因为可能有新的微任务加入到微任务队列)
  6. 如果微任务队列不为空,则继续执行微任务队列中的所有微任务,如此循环。

示例代码

//同步代码
console.log("Start");

//宏任务: 该回调函数的任务会被添加到宏任务队列
setTimeout(() => {
  console.log("宏任务1");
}, 0);

setTimeout(() => {
  console.log("宏任务2");
}, 0);

//微任务
Promise.resolve().then(() => {
  console.log("微任务1");
});

//微任务
Promise.resolve().then(() => {
  console.log("微任务2");
});

//同步代码
console.log("End");

执行结果

Start
End
微任务1
微任务2
宏任务1
宏任务2

执行流程分析

- 先执行同步代码,输出: Start 和 End。
- setTimeout() 创建的宏任务回调被加入 宏任务队列。
- Promise.resolve().then() 创建的微任务回调被加入 微任务队列。
- 同步代码执行完后,开启事件循环,会执行完微任务队列中所有的微任务,输出: 微任务1、 微任务2
- 执行完微任务后,事件循环会从宏任务队列中取出任务执行,输出: 宏任务1、宏任务2

好了👌,异步任务的执行原理你搞清楚了吗?接下来我们学习使用Promise创建异步任务,并处理异步任务的结果。

Promise 处理异步任务

异步任务(如网络请求、文件读写等)的耗时无法预知,因此无法直接在其执行过程中处理后续逻辑。为避免阻塞,我们需要一种机制,在任务完成后自动触发预设的成功/失败处理逻辑。开发者需提前定义这些处理逻辑,程序才能根据结果执行相应操作。 PromiseJavaScript 中用于处理异步操作的对象

Promise它表示一个尚未完成但未来会完成的操作,并提供标准化的方式来处理成功或失败的逻辑。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),Promise对象创建后处于pending状态,异步操作完成后,根据成功或失败转换为fulfilledrejected状态。

基本用法

Promise最基本的用法是通过构造函数实例化一个Promise对象,传入一个带有两个参数的函数,称为executor函数。executor函数接收两个参数:resolvereject,分别表示异步操作成功和失败时的回调函数。

// 1. 创建Promise(泛型声明结果类型)
let promise = new Promise<结果类型>((resolve, reject) => {
    // 异步操作(如网络请求、定时器等)
    if (操作成功) {
        resolve(成功结果);  // ✅ 触发 then()
    } else {
        reject(失败原因);   // ❌ 触发 catch()
    }
});

// 2. 处理结果
promise.then((result: 结果类型) => {
    // 成功逻辑
}).catch((error: Error) => {
    // 失败逻辑
});

示例:以下代码创建了一个Promise对象并模拟了一个异步操作:

const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
  //模拟耗时操作,3秒钟后,根据随机数的结果,处理成功或者失败的逻辑
  setTimeout(() => {
    const num: number = Math.random();
    if (num > 0.5) {
      resolve(num); //成功,执行resolve函数
    } else {
      reject(new Error('Random number is too small'));  //失败,执行reject函数
    }
  }, 2000);
})


promise.then((result: number) => {
  console.log(`Promise resolved with result: ${result}`);
}).catch((error: Error) => {
  console.log(`Promise rejected with error: ${error.message}`);
});

两次执行结果分别如下:

Promise rejected with error: Random number is too small
Promise resolved with result: 0.550281874674772

链式调用

当你调用then()或者catch() 方法时,你可以传入一个回调函数,这个回调函数会在 Promise 状态变为 fulfilled或者reject 时执行。

这个回调函数可以返回一个普通值,也可以返回一个新的 Promise

  • 如果回调函数返回一个普通值,该值会被包装成一个 fulfilled 状态的 Promise,并传递到下一个 then() 方法中。
  • 如果回调函数返回一个 Promise 对象,那么下一个then()将会等待这个新的 Promise 完成后再继续执行。

这使得你可以链式处理异步操作,并且 then() 方法始终返回一个新的 Promise,从而可以继续使用.then()进行链式调用。

示例 1:回调函数返回一个普通值

let promise: Promise<string> = new Promise<string>((resolve, reject) => {
  resolve("Hello");  
})

let promise1: Promise<string> = promise.then((result: string) => {
  return result +" World";
})

promise1.then((result: string) => {
  console.log(result);  //Hello World
})

示例2:回调函数返回一个 Promise

new Promise((resolve, reject) => {
    resolve("Hello");
})
.then(result => {
    console.log(result);  // 输出 "Hello"
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(result + " World");
        }, 1000);
    });  // 返回一个新的 Promise
})
.then(result => {
    console.log(result);  // 输出 "Hello World"(等待 1 秒)
});

异常捕获

你可以在链式调用中使用 .catch() 来捕获和处理任何一步操作中的错误。即使在链中的某个.then()发生了错误,错误会被传递到 .catch() 中。

触发catch()有以下几种情况:

  • Promise处于pending状态时,调用 reject()或者throw抛出异常,会触发catch()

  • Promise处于fulfilled状态时,throw抛出异常或者返回一个拒绝的Promise,会触发 catch()

示例1:当Promise处于pending状态时,throw抛出异常,触发catch

const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
//模拟耗时操作,3秒钟后,根据随机数的结果,处理成功或者失败的逻辑
const num: number = Math.random();
if (num > 0.5) {
  resolve(num); //成功,执行resolve函数
} else {
  //reject(new Error('Random number is too small'));  //失败,执行reject函数
  throw new Error('Random number is too small')
}
})

promise.then((result: number) => {
console.log('Success:', result)
}).catch((error:Error)=>{
console.log('Error:', error.message)
})

[!caution]

如果在pending状态执行异步操作,此时throw抛出异常,不能被catch捕获。要想被catch捕获建议使用reject传递错误数据。

示例2:当Promise处于fulfilled状态时,throw抛出异常,触发catch

let promise = new Promise<string>((resolve, reject) => {
resolve("Success")
})

promise.then(result => {
console.log(result); //输出:Success
throw new Error("出错了...")
}).catch((error: Error) => {
console.log("Error message",error.message); // 输出:出错了。。。
});

示例3:当Promise处于fulfilled状态时,返回一个被拒绝的Promise,触发catch

let promise = new Promise<string>((resolve, reject) => {
  resolve("Success")
})

promise.then(result => {
  console.log(result); //Success
  return new Promise<string>((resolve, reject) => {
    reject(new Error('失败了'))
  })
}).catch((error: Error) => {
  console.log("Error message",error.message);
});

快速创建Promise

1. 方法作用

  • Promise.resolve(value):直接返回一个 已成功(fulfilled) 的 Promise 对象。
  • Promise.reject(reason):直接返回一个 已拒绝(rejected) 的 Promise 对象。

2. 使用场景

//1. 成功的Promise
const successPromise = Promise.resolve('Hello World');
successPromise.then((res: string) => {
  console.log(res); //输出:Hello World
})


//2. 失败的Promise
const errorPromise = Promise.reject("aaa")
errorPromise.catch((error: Error) => {
  console.log('Error message', error.message); //输出:abc
})

其他静态方法

除了 Promise.resolve()Promise.reject(),Promise 还提供了几个强大的静态方法,用于处理多个异步任务或复杂逻辑。以下是它们的核心用法和适用场景:

1. Promise.all() —— 等待所有 Promise 完成
  • 作用:并行执行多个 Promise,全部成功时返回结果数组任意一个失败则立即终止。
  • 适用场景:多个独立异步任务需全部完成(如并发请求多个接口)。
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3); 

Promise.all([p1, p2, p3])
  .then((results: number[]) => console.log(results.toString())) // [1, 2, 3]
  .catch((error: Error) => console.error(error.message)); // 任意一个失败时触发

2. Promise.race() —— 取最快完成的 Promise
  • 作用:返回第一个完成(无论成功或失败) 的 Promise 结果。
  • 适用场景:超时控制、竞速请求(如从多个 CDN 源加载资源)。
const p1:Promise<string> = new Promise(resolve => setTimeout(() => resolve("Winner"), 500));
const p2:Promise<string> = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1000));

Promise.race([p1, p2])
  .then(result => console.log(result))  // "Winner"(p1 更快)
  .catch((error:Error) => console.error(error.message));
3. Promise.any() —— 取第一个成功的 Promise
  • 作用:返回 第一个成功 的 Promise 结果,全部失败时才触发 catch
  • 应用场景:冗余请求(如多个备用 API 源,取最快可用的)。
const p1 = Promise.reject("Error 1");
const p2 = Promise.resolve("Success");
const p3 = Promise.reject("Error 2");

Promise.any([p1, p2, p3])
  .then(result => console.log(result))  // "Success"(第一个成功的)
  .catch((errors:Error) => console.error(errors.message)); // 全部失败时触发(AggregateError)

特点
✅ 忽略失败,只关注第一个成功结果
❌ 全部失败时返回 AggregateError(包含所有错误原因)

await/sync

async/await是用于处理异步操作的Promise语法糖,使编写异步代码更加简单和易读。使用async关键字声明异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步方式编写异步操作的代码。

async 函数声明

  • 函数前加 async 关键字,表示该函数包含异步操作,无论函数内返回什么,都会被包装成 Promise返回。
async function fetchData(){
  return 'Hello World'	// 等价于 Promise.resolve("data")
}

let promise: Promise<string> = fetchData()
promise.then((result:string)=>{
  console.log("成功",result)
})

await 等待 Promise 完成

  • await 只能在 async 函数内部使用。它会暂停代码执行,直到 Promise 变为 fulfilledrejected
async function fetch(url: string) {
  console.log("模拟网络请求中...")
  return "{'name': 'zhangsan', 'age': 20}"
}

async function getUser():Promise<string> {
  const user:string = await fetch("/api/user"); // 等待 Promise 完成
  console.log("用户信息:", user)
  return user
}

getUser().then((result:string)=>{
  console.log(result)
})

[!warning]

await 会阻塞代码,导致多个异步操作串行执行。可以使用 Promise.all() 让多个异步任务并行执行

try/catch 处理异常

  • try/catch 捕获 await 后的 Promise 失败。
async function fetch(url: string) {
  let randomNumber = Math.random()
  if (randomNumber >= 0.5) {
    return "{'name':'张三', 'age':30}" //等价于:Promise.resolve(...)
  } else {
    return Promise.reject("请求失败")
  }
}

async function loadData() {
  try {
    const data = await fetch("/api/data");	//如果fetch返回拒绝的Promise则,会触发catch
    console.log(data); 
  } catch (error) {
    console.error("Error messaage", error); // 捕获 fetch 或 json() 的错误
  }
}

//调用loadData
loadData() 

[!warning]

如果不使用 try/catch,错误会传递到调用方的 .catch()

点击考取鸿蒙开发者认证

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐