1.同步和异步
同步即按顺序执行。
异步即独立执行,异步就是从主线程发射一个子线程来完成任务。
就像这样:

2.回调函数
js从设计之初就是只支持单线程的语言,如何做到异步编程呢?js使用回调函数来解决这个问题。
回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。
常用的异步操作实现方式:
setTimeout定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>dmw</title> </head> <body>
<p>回调函数等待 3 秒后执行。</p> <p id="demo"></p> <script> const print = () => { document.getElementById("demo").innerHTML = "dmw"; }; setTimeout(print, 3000); </script>
</body> </html>
|
这段程序中的 setTimeout 就是一个消耗时间较长(3 秒)的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 “print”,在网页显示“dmw”。
既然 setTimeout 会在子线程中等待 3 秒,在 setTimeout 函数执行之后主线程并没有停止,所以可以这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>dmw</title> </head> <body>
<p>回调函数等待 3 秒后执行。</p> <p id="demo1"></p> <p id="demo2"></p> <script> setTimeout(function () { document.getElementById("demo1").innerHTML="newbee-1!"; }, 3000); document.getElementById("demo2").innerHTML="newbee-2!"; </script>
</body> </html>
|
可以看见一开始显示的“newbee-2”3秒后被 ‘挤’ 下去了。
3.promise
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。
创建 Promise
Promise
对象是通过 new Promise()
构造函数创建的,该构造函数接收一个执行器函数(executor)作为参数。执行器函数有两个参数:resolve
和 reject
,它们都是函数。当异步操作成功完成时,调用 resolve
函数并传入结果;当异步操作失败时,调用 reject
函数并传入错误信息。
示例
1 2 3 4 5 6 7 8 9 10 11
| const myPromise = new Promise((resolve, reject) => { setTimeout(() => { const success = true; if (success) { resolve('操作成功'); } else { reject(new Error('操作失败')); } }, 1000); });
|
使用 Promise
Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>dmw</title> </head> <body> <script>
const fun = new Promise((resolve, reject) => { reject("任务失败了") }) fun.then((data) => { console.log(data) }).catch((err) => { console.log(err) })
</script> </body> </html>
|

或者

Promise 链式调用
Promise
的 .then()
方法会返回一个新的 Promise
对象,这使得你可以进行链式调用,按顺序处理多个异步操作。以下代码先定义两个异步操作函数,第一个函数返回一个promise对象,第一个函数成功调用就会返回promise的resolve,第二个函数就接收这个resolve,与第一个一样,成功调用后就返回它的resolve,最后一个箭头函数接收第二个的resolve并执行打印在控制台的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function asyncOperation1() { return new Promise((resolve) => { setTimeout(() => { resolve('操作 1 完成'); }, 1000); }); }
function asyncOperation2(result) { return new Promise((resolve) => { setTimeout(() => { resolve(result + ', 操作 2 完成'); }, 1000); }); }
asyncOperation1() .then(asyncOperation2) .then((finalResult) => { console.log(finalResult); }) .catch((error) => { console.error(error.message); });
|
Promise.all 和 Promise.race
Promise.all()
:接收一个 Promise
数组作为参数,返回一个新的 Promise
对象。当数组中的所有 Promise
都成功时,新的 Promise
才会成功,其结果是一个包含所有 Promise
结果的数组;只要有一个 Promise
失败,新的 Promise
就会失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const promise1 = Promise.resolve('结果 1'); const promise2 = new Promise((resolve) => { setTimeout(() => { resolve('结果 2'); }, 1000); });
Promise.all([promise1, promise2]) .then((results) => { console.log(results); }) .catch((error) => { console.error(error.message); });
|
Promise.race()
:接收一个 Promise
数组作为参数,返回一个新的 Promise
对象。当数组中的任何一个 Promise
率先成功或失败时,新的 Promise
就会以该 Promise
的结果或错误状态结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const promise3 = new Promise((resolve) => { setTimeout(() => { resolve('结果 3'); }, 2000); }); const promise4 = new Promise((resolve) => { setTimeout(() => { resolve('结果 4'); }, 1000); });
Promise.race([promise3, promise4]) .then((result) => { console.log(result); }) .catch((error) => { console.error(error.message); });
|
更加优雅地书写复杂的异步任务
例如,如果我想分三次输出字符串,第一次间隔 1 秒,第二次间隔 4 秒,第三次间隔 3 秒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>dmw</title> </head> <body> <script> setTimeout(() => { console.log("First") setTimeout( () => { console.log("Second") setTimeout(() => { console.log("Third"); }, 3000) },4000) }, 1000); </script> </body> </html>
|

这段程序实现了这个功能,但是它是用 “函数瀑布” 来实现的。在一个复杂的程序当中,这样写可读性非常差。
来看promise如何实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>dmw</title> </head> <body> <script> new Promise((resolve, reject) => { setTimeout(() => { console.log("First"); resolve(); }, 1000); }).then(() => { return new Promise((resolve, reject) =>{ setTimeout(() => { console.log("Second"); resolve(); }, 4000); }); }).then(() => { setTimeout(() => { console.log("Third"); }, 3000); });
</script> </body> </html>
|
重点注意事项
Promise 链的等待
- 在
.then()
中返回一个新的 Promise
,可以让后续的 .then()
等待这个新的 Promise
完成。
- 如果没有返回
Promise
(如第三个 setTimeout
),后续的 .then()
(如果有)不会等待。
看起来很长,我们改写一下,封装成promise函数,这种返回值为一个 Promise 对象的函数称作 Promise 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const print = (delay, message) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(message); resolve(); }, delay) }) }
print(1000, "First").then(() => { return print(4000, "Second") }).then(() => { print(3000, "Third") })
|
要调用时还可以使用async和await
1 2 3 4 5 6
| const fun = async () => { await print(1000, "First") await print(4000, "Second") await print(3000, "Third") } fun()
|
await 指令后必须跟着一个 Promise,异步函数会在这个 Promise 运行中暂停,直到此promise运行结束再继续运行。
async/await
async/await
是 ES2017(ES8)引入的用于处理异步操作的语法糖,它建立在 Promise
的基础之上,让异步代码看起来更像同步代码,使代码的可读性和可维护性大大提高。下面详细解释其写法和使用方式。
多个异步操作顺序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function asyncOperation1() { return new Promise((resolve) => { setTimeout(() => { resolve('操作 1 完成'); }, 1000); }); }
function asyncOperation2() { return new Promise((resolve) => { setTimeout(() => { resolve('操作 2 完成'); }, 1000); }); }
async function main() { try { const result1 = await asyncOperation1(); console.log(result1); const result2 = await asyncOperation2(); console.log(result2); } catch (error) { console.error(error); } }
main();
|
- 解释
asyncOperation1
和 asyncOperation2
分别返回一个 Promise
,模拟两个异步操作。
- 在
main
函数中,使用 await
依次等待两个异步操作完成。await asyncOperation1()
会暂停函数执行,直到 asyncOperation1
的 Promise
被解决,然后继续执行后续代码。
处理多个并行的异步操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function asyncOperation1() { return new Promise((resolve) => { setTimeout(() => { resolve('操作 1 完成'); }, 1000); }); }
function asyncOperation2() { return new Promise((resolve) => { setTimeout(() => { resolve('操作 2 完成'); }, 1000); }); }
async function main() { try { const [result1, result2] = await Promise.all([asyncOperation1(), asyncOperation2()]); console.log(result1); console.log(result2); } catch (error) { console.error(error); } }
main();
|
解释
Promise.all
用于并行执行多个 Promise
,并在所有 Promise
都解决后返回一个包含所有结果的数组。
- 在
main
函数中,使用 await
等待 Promise.all
的结果。通过解构赋值,将结果分别赋值给 result1
和 result2
。