Promise workflow
1 | function asyncFunction() { |
asyncFunction
这个函数会返回promise对象, 对于这个promise对象,我们调用它的 then
方法来设置resolve后的回调函数, catch
方法来设置发生错误时的回调函数。
该promise对象会在setTimeout之后的 16ms 时被resolve, 这时 then
的回调函数会被调用,并输出 'Async Hello world'
。
在这种情况下 catch
的回调函数并不会被执行(因为promise返回了resolve), 不过如果运行环境没有提供 setTimeout
函数的话,那么上面代码在执行中就会产生异常,在 catch
中设置的回调函数就会被执行。
new Promise
创建promise对象的流程如下所示。
new Promise(fn)
返回一个promise对象- 在
fn
中指定异步等处理- 处理结果正常的话,调用
resolve(处理结果值)
- 处理结果错误的话,调用
reject(Error对象)
- 处理结果正常的话,调用
用Promise来通过异步处理方式来获取XMLHttpRequest(XHR)的数据。
创建XHR的promise对象
首先,创建一个用Promise把XHR处理包装起来的名为 getURL
的函数。
1 | function getURL(URL) { |
getURL
只有在通过XHR取得结果状态为200时才会调用 resolve
- 也就是只有数据取得成功时,而其他情况(取得失败)时则会调用 reject
方法。
resolve(req.responseText)
在response的内容中加入了参数。 resolve方法的参数并没有特别的规则,基本上把要传给回调函数参数放进去就可以了。 ( then
方法可以接收到这个参数值)
熟悉Node.js的人,经常会在写回调函数时将 callback(error, response)
的第一个参数设为error对象,而在Promise中resolve/reject则担当了这个职责(处理正常和异常的情况),所以 在resolve方法中只传一个response参数是没有问题的。
接下来我们来看一下reject
函数。
XHR中 onerror
事件被触发的时候就是发生错误时,所以理所当然调用reject
。 这里我们重点来看一下传给reject
的值。
发生错误时要像这样 reject(new Error(req.statusText));
,创建一个Error对象后再将具体的值传进去。 传给reject
的参数也没有什么特殊的限制,一般只要是Error对象(或者继承自Error对象)就可以。
传给reject
的参数,其中一般是包含了reject原因的Error对象。 本次因为状态值不等于200而被reject,所以reject
中放入的是statusText。 (这个参数的值可以被 then
方法的第二个参数或者 catch
方法中使用)
Promise.resolve
一般情况下我们都会使用 new Promise()
来创建promise对象,但是除此之外我们也可以使用其他方法。
静态方法 Promise.resolve(value)
可以认为是 new Promise()
方法的快捷方式。
比如 Promise.resolve(42);
可以认为是以下代码的语法糖。
1 | new Promise(function (resolve) { |
在这段代码中的 resolve(42);
会让这个promise对象立即进入确定(即resolved)状态,并将 42
传递给后面then里所指定的 onFulfilled
函数。
方法 Promise.resolve(value);
的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then
调用。
1 | Promise.resolve(42).then(function (value) { |
Promise.resolve
作为 new Promise()
的快捷方式,在进行promise对象的初始化或者编写测试代码的时候都非常方便。
Promise.reject
Promise.reject(error)
是和 Promise.resolve(value)
类似的静态方法,是 new Promise()
方法的快捷方式。
比如 Promise.reject(new Error("出错了"))
就是下面代码的语法糖形式。
1 | new Promise(function (resolve, reject) { |
下面这段代码的功能是调用该promise对象通过then指定的 onRejected
函数,并将错误(Error)对象传递给这个 onRejected
函数。
1 | Promise.reject(new Error("BOOM!")).catch(function (error) { |
Promise只能进行异步操作?
在使用Promise.resolve(value)
等方法的时候,如果promise对象立刻就能进入resolve状态的话,那么你是不是觉得 .then
里面指定的方法就是同步调用的呢?
实际上, .then
中指定的方法调用是异步进行的。
1 | let promise = new Promise(function (resolve) { |
执行上面的代码会输出下面的log,从这些log我们清楚地知道了上面代码的执行顺序。
1 | inner promise // 1 |
由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始 <1>
会执行,然后是 resolve(42);
被执行。这时候 promise
对象的已经变为确定状态,FulFilled被设置为了 42
。
下面的代码 promise.then
注册了 <3>
这个回调函数,这是本专栏的焦点问题。
由于 promise.then
执行的时候promise对象已经是确定状态,从程序上说对回调函数进行同步调用也是行得通的。
但是即使在调用 promise.then
注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。
因此 <2>
会最先被调用,最后才会调用回调函数 <3>
。
为了避免同时使用同步、异步调用可能引起的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式 。
promise chain 中传递参数
这时候如果 Task A 想给 Task B 传递一个参数该怎么办呢?
答案非常简单,那就是在 Task A 中 return
的返回值,会在 Task B 执行时传给它。
1 | function doubleUp(value) { |
这段代码的入口函数是 Promise.resolve(1);
,整体的promise chain执行流程如下所示。
Promise.resolve(1);
传递 1 给increment
函数- 函数
increment
对接收的参数进行 +1 操作并返回(通过return
) - 这时参数变为2,并再次传给
doubleUp
函数 - 最后在函数
output
中打印结果
每次调用then都会返回一个新创建的promise对象
每个方法中 return
的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。
return的值会由 Promise.resolve(return的返回值);
进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then
的结果都是返回一个新创建的promise对象。
1 | let aPromise = new Promise(function (resolve) { |
Promise API
Promise.then
promise.then(onFulfilled, onRejected);
1 | let promise = new Promise(function (resolve, reject) { |
这段代码创建一个promise对象,定义了处理onFulfilled和onRejected的函数(handler),然后返回这个promise对象。
这个promise对象会在变为resolve或者reject的时候分别调用相应注册的回调函数。
- 当handler返回一个正常值的时候,这个值会传递给promise对象的onFulfilled方法。
- 定义的handler中产生异常的时候,这个值则会传递给promise对象的onRejected方法。
Promise.catch
promise.catch(onRejected);
1 | let promise = new Promise(function (resolve, reject) { |
这是一个等价于promise.then(undefined, onRejected)
的语法糖。
Promise.resolve
Promise.resolve(promise);
Promise.resolve(thenable);
Promise.resolve(object);
1 | let taskName = "task 1"; |
根据接收到的参数不同,返回不同的promise对象。
虽然每种情况都会返回promise对象,但是大体来说主要分为下面3类。
接收到promise对象参数的时候
返回的还是接收到的promise对象
接收到thenable类型的对象的时候
返回一个新的promise对象,这个对象具有一个
then
方法接收的参数为其他类型的时候(包括JavaScript对或null等)
返回一个将该对象作为值的新promise对象
Promise.reject
Promise.reject(object)
返回一个使用接收到的值进行了reject的新的promise对象。
而传给Promise.reject的值也应该是一个 Error
类型的对象。
另外,和 Promise.resolve不同的是,即使Promise.reject接收到的参数是一个promise对象,该函数也还是会返回一个全新的promise对象。
1 | var r = Promise.reject(new Error("error")); |
Promise.all
Promise.all(promiseArray);
1 | let p1 = Promise.resolve(1), |
生成并返回一个新的promise对象。
参数传递promise数组中所有的promise对象都变为resolve的时候,该方法才会返回, 新创建的promise则会使用这些promise的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的promise对象。
由于参数数组中的每个元素都是由 Promise.resolve
包装(wrap)的,所以Paomise.all可以处理不同类型的promose对象。
Promise.race
Promise.race(promiseArray);
1 | let p1 = Promise.resolve(1), |
生成并返回一个新的promise对象。
参数 promise 数组中的任何一个promise对象如果变为resolve或者reject的话, 该函数就会返回,并使用这个promise对象的值进行resolve或者reject。