异步JavaScript简介
异步编程是我们在等待一个长期任务完成得同时可以执行其他命令,不必等待长期任务的完成。同时,你的程序将在任务完成后显示成果。
浏览器有许多功能需要很长时间来完成,因此,异步编程是必要的,例如:
- 使用
fetch()
发起HTTP请求 - 使用
getUserMedia
访问用户的摄像头和麦克风 - 使用
showOpenFilePicker
请求用户选择文件以供访问
事件处理程序
事件处理程序实际上就是异步编程的一种形式:你提供的函数将在事件发生时被调用,而不是立刻被调用。若“事件”是异步操作已经完成,那么异步操作的调用结果就会被事件传递给调用者。
一些早期的异步API,如XMLHttpRequest
API就是以这种方式来使用事件的。XMLHttpRequest
可以让你用JavaScript
向远程服务器发起HTTP请求。这样的操作通常需要很长时间,所以它被设计成异步API。我们可以给XMLHttpRequest
对象附加时间监听器来让程序在请求进展和最终完成时获得通知。
回调
时间处理程序时一种特殊的回调函数。回调函数,即一个被传递到另一个函数中的会在适当的时候被调用的函数。回调函数曾是JavaScript中实现异步函数的主要方式。
然而,当回调函数本身需要调用其他同样接受回调函数的函数时,基于回调的代码会非常难以理解。
1 | function doStep1(init, callback) { |
该函数深度嵌套,非常难以阅读和调试。事实上,JavaScript中异步编程的基础是Promise
。
Promise
Promise
是现代JavaScript中异步编程的基础,是一个由异步函数返回的可以向我们只是当前操作所处状态的对象。在Promise
返回给调用者时,操作往往没有完成,但Promise
对象可以让我们在操作最终完成时对其进行处理。下面,我们以fetch
API为例:
fetch() API
fetch()
是一个现代的,基于Promise
的,用于替代XMLHttpRequest
的方法。
由如下代码:
1 | const fetchPromise = fetch( |
这里:
- 调用
fetch()
API,并将返回值赋值给fetchPromise
变量。 - 输出
fetchPromise
变量,输出结果为Promise {<state>: "pending"}
。这里输出的是Promise
对象的信息:它有一个state
属性,值为"pending"
。"pending"
状态意味着操作仍在进行。 - 将一个处理函数传递给
Promise
的then
方法,若操作成功,Promise
将调用我们的处理函数,传去一个包含服务器响应的Response
对象。 - 输出一条成功信息。
完整的输出如下:
1 | Promise { <state>: "pending" } |
链式使用Promise
在我们通过fetch()
API得到一个Respose
对象时,我们需要调用另外一个函数来获取响应数据。若我们向获取json
格式,我们可以调用Response
对象的json()
方法。事实上,json()
也是异步的,所以我们要调用两个异步函数:
1 | const fetchPromise = fetch( |
在上面的代码中,我们给fetch()
返回的Promise
对象中添加了一个then()
处理程序。在这个处理程序中,我们调用了json()
方法(该方法返回一个Promise
对象),然后将新的then()
处理程序传递到response.json()
返回的Promise
中。
Promise
的优雅之处在于then()
本身也可以返回一个Promise
,这个Promise
将指示then()
中调用的异步函数的完成状态。(即将then()
内调用的异步函数返回的Promise
显式的作为then()
的返回值,看作then()为一个异步函数).
改写上方代码:
1 | const fetchPromise = fetch( |
当然,Promise
的状态不总是程序成功执行。若其状态码不是OK,我们就抛出一个错误:
1 | const fetchPromise = fetch( |
错误捕获
Promise
提供了一个catch()
方法来支持错误处理。异步操作成功时,参数被传入then()
,若失败,则将错误信息传入catch()
。
如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。、
1 | const fetchPromise = fetch( |
Promise术语
Promise 有三种状态:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。这是调用 fetch() 返回 Promise 时的状态,此时请求还在进行中。
- 已兑现(fulfilled):意味着操作成功完成。当 Promise 完成时,它的 then() 处理函数被调用。
- 已拒绝(rejected):意味着操作失败。当一个 Promise 失败时,它的 catch() 处理函数被调用。
有时我们用已敲定(settled)这个词来同时表示已兑现(fulfilled)和已拒绝(rejected)两种情况。
如果一个Promise
处于resolved状态,或者它被“锁定”以跟随另一个Promise
的状态,那么它就是fulfilled。