异步任务概述
ArkTS的异步任务沿用了JavaScript和TypeScript的异步任务机制,此文章以JavaScript语言为基础讲解异步任务相关概念和使用。文章纯属个人理解有不正确的地方欢迎指正。
在学习异步任务之前,我们先对“任务”这个词下一个定义——“任务是完成某项操作要执行的一段代码”。
一个任务可能是简单的打印语句,也可以是复杂的算法处理、网络请求或 I/O 操作等。这些任务的执行时间各不相同:打印语句执行时间通常很短,而网络请求和 I/O 操作可能耗时较长。
由于 JavaScript 执行引擎是单线程的,如果按照顺序执行所有任务,耗时较长的任务可能会阻塞线程,导致整体性能下降,进而影响用户体验。
为了更高效地管理和执行这些任务,JavaScript 将要执行的任务分为同步任务和异步任务,使得JavaScript 能够在单线程环境中有效地处理耗时操作,而不阻塞主线程。
同步任务:这些是立即执行的任务,不需要等待任何条件或事件,会阻塞后续代码的执行。
异步任务:这些任务不会立即执行,而是先挂起,被推迟到未来某个时间点执行,不会阻塞后续代码的执行。
示例代码
在 ES6(ECMAScript 2015,JavaScript标准规范) 之前,JavaScript 的异步任务主要是通过如 setTimeout, setInterval, I/O 操作 等)来实现。
//同步代码
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
异步任务3 (1 秒后开始循环输出)
异步任务3 (再次循环输出)
异步任务3 (继续每秒输出)
事件循环机制
异步任务之所以能够被推迟到未来的某个时间点执行,而不阻塞主线程,主要依赖于事件循环机制和任务队列来实现的。
事件循环机制:是一种调度异步任务的模型。
任务队列:任务队列是用来存储异步任务的容器,等待后续某个时间点,被事件循环调度。
下图展示了JavaScript执行引擎启动后,各个任务的执行流程。
在 ES6 之前(ECMAScript 2015,JavaScript标准规范),所有异步任务都被放在同一个任务队列中,缺乏更精细的控制,所以从ES6开始引入了宏任务和微任务对异步任务进行更精细的控制。
宏任务和微任务
从ES6开始引入了大量的新特性(包括 Promise 和 微任务)。引入微任务是为了优化任务调度,减少延迟,提高性能,可以更高效地处理一些高优先级的小任务。从此异步任务被细分为宏任务和微任务。
微任务:具有高优先级的异步任务,通常用于较短时间内的异步操作,如 Promise 等操作。
宏任务:具有较低优先级的异步任务,通常用于延迟执行或需要等待的操作,如 setTimeout 等操作。
这种任务划分和管理机制使得 JavaScript
能够在单线程环境中有效地处理异步任务,提升程序的响应性和用户体验。
JavaScript 执行引擎通过事件循环机制、宏任务队列、微任务队列在管理并执行异步任务。如下图所示
解释:
JavaScript 执行引擎启动后,开启一条主线程,按照代码顺序依次往下执行;
当执行到宏任务时,将宏任务添加到宏任务队列,等待后续事件循环调度,JavaScript主线程继续往下执行;
当执行到微任务时,将微任务添加到微任务队列,等待后续事件循环调度,JavaScript主线程继续往下执行,直到所有同步代码执行完毕;
执行完所有同步代码之后,启动事件循环,先依次执行完微任务队列中所有的微任务,直到微任务微空。
再执行一个宏任务后,判断微任务队列是否为空(因为可能有新的微任务加入到微任务队列)
如果微任务队列不为空,则继续执行微任务队列中的所有微任务,如此循环。
示例代码
//同步代码
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 是一种用于处理异步操作的 JavaScript 对象。它代表一个未来可能完成(或失败)的操作及其结果值。
Promise 是一种用于处理微任务的工具,同时Promise也是一个对象,它有三种状态:pending(待定)、fulfilled(已完成)和 rejected(已拒绝)。这些状态决定了 Promise 对象如何处理异步任务的结果。
pending(待定):
这是 Promise 对象的初始状态,表示异步操作还没有完成。这个状态下,Promise 既没有成功也没有失败。
fulfilled(已完成):当异步操作成功完成时,Promise 会从 pending 状态变为 fulfilled 状态。成功时调用 resolve(),并且 resolve() 的参数会作为后续 .then() 方法的回调函数的参数。
rejected(已拒绝):当异步操作失败时,Promise 会从 pending 状态变为 rejected 状态。失败时调用 reject(),并且 reject() 的参数会作为后续 .catch() 方法的回调函数的参数。
⚠️ 注意:Promise 对象一旦从 pending 状态转换为 fulfilled 或 rejected,就会被锁定在这个状态,无法再改变状态。也就是说,Promise 一旦被 resolve 或 reject,它的状态就不会再发生变化。
Promise 是一个对象,它封装了异步操作的最终结果,并提供了两种回调函数来处理成功和失败的情况。这两个回调函数分别是:
const promise = new Promise((resolve, reject) => {
// 模拟异步操作
let success = true;
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
});
在上述代码中,promise 对象的状态会根据 success 的值变为 fulfilled 或 rejected。
使用 .then() 和 .catch() 处理结果
一旦 Promise 的状态改变,你可以使用 .then() 来处理成功的结果,使用 .catch() 来处理失败的原因。
then():用来指定当 Promise 成功完成时需要执行的回调函数。
catch():用来指定当 Promise 失败时需要执行的回调函数。
promise
.then(result => {
console.log(result); // "操作成功!"
})
.catch(error => {
console.log(error); // "操作失败!"
});
链式调用
当你调用 then() 或者catch() 方法时,你可以传入一个回调函数,这个回调函数会在 Promise 状态变为 fulfilled或者reject 时执行。这个回调函数可以返回一个普通值,也可以返回一个新的 Promise。
如果回调函数返回一个普通值,该值会被包装成一个 resolved 状态的 Promise,并传递到下一个 then() 方法中。
如果回调函数返回一个 Promise 对象,那么下一个 then() 将会等待这个新的 Promise 完成后再继续执行。
这使得你可以链式处理异步操作,并且 then() 方法始终返回一个新的 Promise,从而可以继续使用 .then() 进行链式调用。
示例1:回调函数返回一个普通值
在这个例子中,then() 回调函数返回了一个普通值 “World”。这个值被自动包装为一个已解决的 Promise,然后传递给下一个 .then() 回调函数。
new Promise((resolve, reject) => {
resolve("Hello");
})
.then(result => {
console.log(result); // 输出 "Hello"
return "World"; // 返回一个普通值,自动风作为resolved("World")状态的Promise对象
})
.then(result => {
console.log(result); // 输出 "World"
});
示例 2:回调函数返回一个 Promise
在这个例子中,then() 回调函数返回了一个新的 Promise 对象。下一个 then() 会等待这个 Promise 被解析(resolve(“World”))之后,再继续执行回调。
new Promise((resolve, reject) => {
resolve("Hello");
})
.then(result => {
console.log(result); // 输出 "Hello"
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("World");
}, 1000);
}); // 返回一个新的 Promise
})
.then(result => {
console.log(result); // 输出 "World"(等待 1 秒)
});
异常捕获
你可以在链式调用中使用 .catch() 来捕获和处理任何一步操作中的错误。即使在链中的某个 .then() 发生了错误,错误会被传递到 .catch() 中。
触发 catch 的方式有下面几种情况:
触发 catch 的方式有下面几种情况:
当Promise处于pending状态时,手动调用 reject(),将 Promise 设为 rejected 状态, 触发catch;
当Promise处于fulfilled状态时,通过返回一个被拒绝的 Promise,它会导致 Promise 进入 rejected 状态,从而触发 catch。
主动通过throw抛出异常,会让当前的异步操作失败,从而触发 catch
示例1:当Promise处于pending状态时,throw抛出异常,触发catch
new Promise((resolve, reject) => {
throw new Error("Something went wrong");
})
.catch(error => {
console.log(error.message); // 输出 "Something went wrong"
});
示例2:当Promise处于fulfilled状态时,throw抛出异常,触发catch
new Promise((resolve, reject) => {
resolve("Success")
})
.then(result=>{
console.log(result); //Success
throw new Error("出错了。。。")
})
.catch(error => {
console.log(error); //出错了。。。
});
示例3:当Promise处于fulfilled状态时,返回一个被拒绝的Promise,触发catch
new Promise((resolve, reject) => {
resolve("Success")
})
.then(result=>{
console.log(result); //Success
return new Promise((resolve,reject)=>{
reject("Failed。。。")
})
})
.catch(error => {
console.log(error); //Failed。。。
});
Promise.resolve() 是一个用于快速创建已解决的 Promise 对象的工具方法,能够处理普通值、Promise 对象。
// value:可以是任意值,也可以是一个 Promise 对象。
Promise.resolve(value);
如果value传入的值已经是一个 Promise,则直接返回该 Promise;如果传入的值是一个普通值(如字符串、数字等),则返回一个已解决的 Promise,并将该值作为结果传递给 then() 的回调函数。
示例1:传入普通值
//Promise.resolve(42) 返回一个已经解决的 Promise,并将 42 作为结果传递给 then() 回调。
const promise = Promise.resolve(42);
promise.then(result => {
console.log(result); // 输出 42
});
示例2:传入另一个已解决的 Promise
const existingPromise = Promise.resolve("Hello, World!");
// 直接返回传入的 existingPromise,因为它已经是一个 Promise,所以不会创建新的 Promise。
const promise = Promise.resolve(existingPromise);
promise.then(result => {
console.log(result); // 输出 "Hello, World!"
});
与 Promise.resolve() 类似,Promise.reject() 用于快速创建一个被拒绝的 Promise,并且可以传递拒绝的原因。
//reason:拒绝的原因,可以是任意类型的值(通常是一个错误对象或错误消息),它将作为 catch() 方法的回调函数的参数。
Promise.reject(reason);
Promise.reject() 返回一个 Promise 对象,它的状态被立即设置为 rejected,并将传入的拒绝原因作为参数传递给 catch() 回调函数。
示例1: 返回一个已经拒绝的Promise
这里,Promise.reject(“Something went wrong”) 返回一个已拒绝的 Promise,并将 “Something went wrong” 作为错误原因传递给 catch() 方法。
const promise = Promise.reject("Something went wrong");
promise.catch(error => {
console.log(error); // 输出 "Something went wrong"
});
示例2:返回一个已拒绝的 Promise,拒绝原因为 Error 对象
这里,Promise.reject(new Error(“Network Error”)) 返回一个已拒绝的 Promise,并将 Error 对象传递给 catch() 方法。catch() 中可以通过 error.message 获取错误的具体信息。
const promise = Promise.reject(new Error("Network Error"));
promise.catch(error => {
console.log(error.message); // 输出 "Network Error"
});
示例3:链式调用中的错误传播
在 Promise 链中的任何一个 Promise 被拒绝时,后续的 catch() 会捕获到该错误,即使该拒绝发生在链中的任意一环。
Promise.resolve("Start")
.then(result => {
console.log(result);
return Promise.reject("Error occurred");
})
.catch(error => {
console.log(error); // 输出 "Error occurred"
});
本文来源誉天王琦老师编写!
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。