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 提供的类,目的是更加优雅地书写复杂的异步任务。
例如,如果我想分三次输出字符串,第一次间隔 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 类有 .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>
|

或者
