前言
Javascript异步编程可以算是JS的难点之一。下面就异步编程方法之一的Promise进行详细介绍和总结。但说到Promise之前,我会简单提一下什么是JS异步和回调函数。
JS异步
JS异步是指在进行某些需要耗时不会立即返回结果的操作时,不会阻塞后面的操作,一旦该耗时的操作完成时,则会通知需要调用其结果的函数来做后续处理。这是一种异步非阻塞的操作,也就是说任务的排列顺序和执行任务是不一致的。
回调函数
和同步操作不同,异步操作即不会立即返回结果的操作(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?
通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)。
如果不用callback,由于js会立即执行后面console.log
,导致打印出来的photo
为undefined
:
var photo = downloadPhoto('http://coolcats.com/cat.gif')
console.log(photo) //undefined
复制代码
使用callback时,我们将handlePhoto
当做callback传入downloadPhoto
这个异步函数中,那么当图片下载行为结束后,无论是成功还是失败,都会执行到handlePhoto
,对photo
或者是error
进行处理。
downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
function handlePhoto (error, photo) {
if (error) console.error('Download error!', error)
else console.log('Download finished', photo)
}
console.log('Download started')
复制代码
那假如callback函数同样是个异步函数,且callback里又嵌入了callback呢?
如此一来,嵌套太深容易引发“回调地狱
”,即代码只会横向发展,不好管理。
为此,对于那些需要连续执行的异步操作,Promise可以是一种很好的解决办法。
Promise
Promise的概念对于初学者来说一直很抽象,我们可以举个例子: 比如你是个经销商,你要去工厂订货,拿到货后你才能自己销售。那么你和工厂之前立下一个契约,保证工厂在在完成生产后通知你,或者就算是因某种原因出错了而无法生产也会通知到你。那么此时这里的契约就相当于我们要讲述的promise,promise就像是个特殊的对象,连接了工厂的生产行为和你的消费行为,是生产者和消费者间的纽带。
Promise的创建
Promise创建时,会传给promise一个称为excutor
执行器的函数。这个excutor
我们可以理解为生产者的生产过程函数。这个函数含有两个参数resolve
和reject
,这俩参数也同样是函数,用来传递异步操作的结果。语法如下:
let promise = new Promise(function(resolve, reject) {
// executor
})
复制代码
有几点值得说一下:
- 在promise对象创建时,
excutor
会立即执行。 - 在
resolve
和reject
是JS引擎自动创建的函数,我们无需自己创建,只需将其作为参数传入就好。 - 创建的
promise
的内部状态是个对象,初始时为:
{
state, //pending
result, //undefined
}
复制代码
一旦exucutor
执行完,要么产生value
,要么产生error
,此时会立即调用resolve
(当产生value
时)或者调用reject
(当产生error
)时,内部状态也会随之改变,如下图所示:
注意,当excutor
里面即使调用了多个resolve
和reject
,其最终还是只执行一个,其他的都被忽略掉。
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
复制代码
Promise的then,catch和finally
在上面例子中,既然生产者的行为完成了,结果也传递出去了,那么如何通知消费者呢?我们可以使用.then
, .catch
, 和.finally
来注册消费者的函数,把.then
,.catch
,.finally
看做是个订阅列表,将消费者的函数注册于此,一旦收到结果时,就可以通知到对方进行相应的处理。
- .then的用法如下:
promise.then(
result = > resultHandle(result)
error = > errorHandle(error)
)
复制代码
.then()
接收2个函数,一个用来处理正常结果result
,一个用来处理error
,但一般情况下,我们也可以不用在.then()
中传入这个error
处理的函数。
- .catch的用法如下:
promise.catch(
error = > errorHandle(error)
)
复制代码
其实这就是相当于promise.then(null, errorHandle)
。
.catch
会捕获到整个异步操作中,或者是一系列连续的异步操作链中的出现的任何类型的错误,一旦抛出错误,则会直接转入到.catch
中进行错误处理。
- .finally的用法如下:
promise.finally(
finalHandle
)
复制代码
当promise的状态确定时,即无论拿到的是正常结果还是错误信息,总会执行这个finalHandle
函数。用.finally
可以做一些清理操作,比如发起网络请求后,可以停止loading显示。
Promise链式调用
当有一系列的异步操作需要一个接一个执行时,可以使用promise的调用链。
举个例子,我们用fetch
这个方法去发起网络请求,fetch()
返回的是一个promise对象,那么我们可以对其连续地调用.then
来进行一步步连续地异步操作。注意:promise.then()
返回的是一个新的promise对象,所以我们才可以继续对其调用.then
。
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to github
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
}).catch(error => console.log(error));
复制代码
这段代码的作用就是先发起网络请求获取到服务端的相应内容(其实只是响应头),然后通过调用response.json()
继续获取response
完整的远程数据并将其解析为JSON格式(也是异步操作),接着根据json中user的name信息,继续发起网络请求,拿到用户object及其头像url,展示其头像并在3秒后删除头像图片。.catch
会处理上面一系列流程中出现的任何错误。
值得注意的是,promise.then( handleFunction )
中的handleFunction
可以返回立即值,也可以返回promise
对象。如果返回立即值,则可以直接把结果传入到下一步的.then
进行处理,但是如果返回的是promise
对象,那么一定要等到这个返回的promise
处理完,拿到结果后,才会进行下一步的.then
处理!可以用下图加以理解:
总结
在JS异步编程中,Promise相对于callback,具有更优的代码流,并且具有很好的灵活性。Promise符合自然的事物执行顺序,即先做异步操作,然后再用.then
告知下一步该做什么。而在Callback的用法中,先得知道下一步做什么,然后才能将其作为callback函数传入异步操作函数中。而且,promise在得到结果后,可以通知到多个后续的结果处理函数,.then
就像一个订阅列表一样。而在callback的用法中,只能传入1个callback函数。
参考链接
Promise: javascript.info/promise-bas…
Promise MDN文档: developer.mozilla.org/en-US/docs/…
Callback Hell:callbackhell.com/