Promise是JavaScript的异步编程模式,为繁重的异步回调带来了福音。
一直以来,JavaScript处理异步都是以callback的方式,假设需要进行一个异步队列,执行起来如下:
animate (ball1, 100, function () { animate (ball2, 200, function () { animate (ball3, 300, function () { animate (ball3, 150, function () { animate (ball2, 150, function () { animate (ball1, 150, function () { // do something }) }) }) }) })
这就是所谓的回调金字塔,当你想突然增加一个时,你要去算括号,维护起来相当麻烦。
但如果换成以下这种形式:
promiseAnimate(ball1,100) .then(function(){ return promiseAnimate(ball2,200) }) .then(function(){ return promiseAnimate(ball3,300) }) .then(function(){ return promiseAnimate(ball3,150) }) .then(function(){ return promiseAnimate(ball2,150) }) .then(function(){ return promiseAnimate(ball1,150) })
看起来就清晰,舒服多了,而且随意插入,无需顾忌。
接下来,说说这货怎么用。
1.1 Promise分三类:
1.Constructor
Promise类似于 XMLHttpRequest
,从构造函数 Promise
来创建一个新建新promise
对象作为接口。
要想创建一个promise对象、可以使用new
来调用Promise
的构造器来进行实例化。
var promise = new Promise(function(resolve, reject) { // 异步处理 // 处理结束后、调用resolve 或 reject });
2.Instance Method
对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数 可以使用promise.then()
实例方法。
promise.then(onFulfilled, onRejected)
- resolve(成功)时
-
onFulfilled
会被调用 - reject(失败)时
-
onRejected
会被调用
onFulfilled
、onRejected
两个都为可选参数。
promise.then
成功和失败时都可以使用。 另外在只想对异常进行处理时可以采用 promise.then(undefined, onRejected)
这种方式,只指定reject时的回调函数即可。 不过这种情况下 promise.catch(onRejected)
应该是个更好的选择。
promise.catch(onRejected)
3.Static Method
像 Promise
这样的全局对象还拥有一些静态方法。
包括 Promise.all()
还有 Promise.resolve()
等在内,主要都是一些对Promise进行操作的辅助方法。
看到上面,如果不懂可以忽略,后面在看回来看。
接下来,举一个栗子:
function asyncFunction() { return new Promise(function (resolve, reject) { setTimeout(function () { resolve('Async Hello world'); }, 16); }); } asyncFunction().then(function (value) { console.log(value); // => 'Async Hello world' }).catch(function (error) { console.log(error); // =>如果出错,将打印错误 }); //new Promise构造器之后,会返回一个promise对象 //为promise对象用设置 .then 调用返回值时的回调函数。
asyncFunction
这个函数会返回promise对象, 对于这个promise对象,我们调用它的 then
方法来设置resolve后的回调函数, catch
方法来设置发生错误时的回调函数。(相当于正确执行then,错误执行catch)
上面的处理还可以写成第二类的形式:
asyncFunction().then(function (value) { console.log(value); // => 'Async Hello world' }, function (error) { console.log(error); // =>'错误执行这里' });
1.2 Promise的状态:
"has-resolution" - Fulfilled
resolve(成功)时。此时会调用 onFulfilled
"has-rejection" - Rejected
reject(失败)时。此时会调用 onRejected
"unresolved" - Pending
既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等
初始化后,正确执行,错误执行。
1.3 编写Promise代码
1.创建Promise对象
-
new Promise(fn)
返回一个promise对象 -
在
fn
中指定异步等处理-
处理结果正常的话,调用
resolve(处理结果值)
-
处理结果错误的话,调用
reject(Error对象)
-
2.编写Promise对象处理方法
XXX.then(function onFulfilled(){ //XXX 返回Promise对象
//正确执行
}).catch(function onRejected (error){
//错误执行
})
例子如下:
function getURL(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } // 运行示例 var URL = "http://httpbin.org/get"; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); }); //可以在 Google的console 环境下运行
2.实战Promise(Promise.resolve 和 Promise.reject)
2.1 Promise.resolve
一般我们会用 new Promise() 创建对象
例如:
new Promise(function(resolve){ resolve(42); });
但可以用 以下的方法 简化:
Promise.resolve(42);
在这段代码中的 resolve(42);
会让这个promise对象立即进入确定(即resolved)状态,并将 42
传递给后面then里所指定的 onFulfilled
函数。
方法 Promise.resolve(value);
的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then
调用。
Promise.resolve(42).then(function(value){ console.log(value); // =>42 });
2.2 Promise.reject
对应前面的用法,我们可以变通一下
new Promise(function(resolve,reject){ reject(new Error("出错了")); });
简化:
Promise.reject(new Error("出错了"))
这段代码的功能是调用该promise对象通过then指定的 onRejected
函数,并将错误(Error)对象传递给这个 onRejected
函数。
Promise.reject(new Error("BOOM!")).catch(function(error){ console.error(error); });
2.3 Promise#then
大家已经学会了 .then().catch() 的写法,如果方法链变得更长:
function taskA() { console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); // Task A // Task B // Final Task
taskA() taskB() finalTask() 顺利执行 因为A B没有发生错误,所以没有被执行到。 如果将 taskB() 故意写成错误的
function taskA() { console.log("Task A"); } function taskB() { console.log1111("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); // Task A // Catch Error: A or B,TypeError: Object [object Object] has no method 'log11' // Final Task
taskA()正常 onRejected抛出A或B产生的错误
2.3.1 Promise 传值
想要传值,只需要return 示例:
function doubleUp(value) { return value * 2; } function increment(value) { return value + 1; } function output(value) { console.log(value);// => (1 + 1) * 2 } var promise = Promise.resolve(1); promise .then(increment) .then(doubleUp) .then(output) .catch(function(error){ // promise chain中出现异常的时候会被调用 console.error(error); });
这段代码的入口函数是 Promise.resolve(1);
,整体的promise chain执行流程如下所示。
-
Promise.resolve(1);
传递 1 给increment
函数 -
函数
increment
对接收的参数进行 +1 操作并返回(通过return
) -
这时参数变为2,并再次传给
doubleUp
函数 -
最后在函数
output
中打印结果
每个方法中 return
的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。
return的值会由 Promise.resolve(return的返回值);
进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then
的结果都是返回一个新创建的promise对象。
2.4 Promise#Catch
这里的Promise#catch是 promise.then(undefined, onRejected);
方法的一个别名而已,用法如下:
var promise = Promise.reject(new Error("message")); promise.catch(function (error) { console.error(error); });
但是在IE8及以下版本则会出现 identifier not found 的语法错误,因为catch是保留字有关。
解决方法有两种:
1.使用[]代替.
var promise = Promise.reject(new Error("message")); promise["catch"](function (error) { console.error(error); });
2.使用原始写法
var promise = Promise.reject(new Error("message")); promise.then(undefined, function (error) { console.error(error); })
2.5 每次调用then都会返回一个新创建的promise对象
then 或 catch 方法每次都会创建并返回一个新的promise对象 先看例子:
// 1: 对同一个promise对象同时调用 `then` 方法 var aPromise = new Promise(function (resolve) { resolve(100); }); aPromise.then(function (value) { return value * 2; }); aPromise.then(function (value) { return value * 2; }); aPromise.then(function (value) { console.log("1: " + value); // => 100 }) // vs // 2: 对 `then` 进行 promise chain 方式进行调用 var bPromise = new Promise(function (resolve) { resolve(100); }); bPromise.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("2: " + value); // => 100 * 2 * 2 });
第1种写法中并没有使用promise的方法链方式,这在Promise中是应该极力避免的写法。这种写法中的 then
调用几乎是在同时开始执行的,而且传给每个 then
方法的 value
值都是 100
。
第2中写法则采用了方法链的方式将多个 then
方法调用串连在了一起,各函数也会严格按照 resolve → then → then → then 的顺序执行,并且传给每个 then
方法的 value
的值都是前一个promise对象通过 return
返回的值。
then
的错误使用方法function badAsyncCall() { var promise = Promise.resolve(); promise.then(function() { // 任意处理 return newVar; }); return promise; }
这种写法有很多问题,首先在 promise.then
中产生的异常不会被外部捕获,此外,也不能得到 then
的返回值,即使其有返回值。
由于每次 promise.then
调用都会返回一个新创建的promise对象,因此需要像上述方式2那样,采用promise chain的方式将调用进行链式化,修改后的代码如下所示。
then
返回返回新创建的promise对象function anAsyncCall() { var promise = Promise.resolve(); return promise.then(function() { // 任意处理 return newVar; }); }
2.6 Promise.all
Promise.all
接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then
方法
首先是一个没有使用.all 的例子:
function getURL(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } // 发送XHR请求 var request = { comment: function getComment() { return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse); }, people: function getPeople() { return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse); } }; function main() { function recordValue(results, value) { results.push(value); return results; } // [] 用来保存初始化的值 var pushValue = recordValue.bind(null, []); return request.comment().then(pushValue).then(request.people).then(pushValue); } // 运行的例子 main().then(function (value) { console.log(value); }).catch(function(error){ console.error(error); });
发送两个XHR请求,然后mian方法顺序处理,看到main()里面的写法 估计都是晕晕的。
使用.all之后
function getURL(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var request = { comment: function getComment() { return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse); }, people: function getPeople() { return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse); } }; function main() { return Promise.all([request.comment(), request.people()]); } // 运行示例 main().then(function (value) { console.log(value); }).catch(function(error){ console.log(error); });
mian()里面简洁多了,也很好理解了
在上面的代码中,request.comment()
和 request.people()
会同时开始执行,而且每个promise的结果(resolve或reject时传递的参数值),和传递给 Promise.all
的promise数组的顺序是一致的。
main().then(function (results) { console.log(results); // 按照[comment, people]的顺序 });
以下代码可以看得出.all是同时执行的:
// `delay`毫秒后执行resolve function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } var startDate = Date.now(); // 所有promise变为resolve后程序退出 Promise.all([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (values) { console.log(Date.now() - startDate + 'ms'); // 約128ms console.log(values); // [1,32,64,128] });
这个promise对象数组中所有promise都变为resolve状态的话,至少需要128ms。实际我们计算一下Promise.all
的执行时间的话,它确实是消耗了128ms的时间。
从上述结果可以看出,传递给 Promise.all
的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。
2.7 Promise.race
Promise.race
只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理
// `delay`毫秒后执行resolve function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); console.log(delay+',') }, delay); }); } // 任何一个promise变为resolve或reject 的话程序就停止运行 但不会取消其他Promise的执行 Promise.race([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (value) { console.log(value); // => 1, 32, 1 64, 128, });
到了最后,估计您已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子。
以上文章是参考 JavaScript Promise 迷你书,想更加深入的同学请移步这里。