一下内容摘自http://liubin.org/promises-book/#ch2-promise-race,JavaScript Promise迷你书(中文版)
1.什么是promise
Promise是抽象异步处理对象以及对其进行各种操作的组件。 其详细内容在接下来我们还会进行介绍,Promise并不是从JavaScript中发祥的概念。
Promise最初被提出是在 E语言中, 它是基于并列/并行处理设计的一种编程语言。
现在JavaScript也拥有了这种特性,这就是JavaScript Promise。
说到基于JavaScript的异步处理,首先都会想到利用回调函数。
getAsync("fileA.txt", function(error, result){
if(error){// 取得失败时的处理
throw error;
}
// 取得成功时的处理
});
Node.js等则规定在JavaScript的回调函数的第一个参数为 Error 对象,这也是它的一个惯例。
像上面这样基于回调函数的异步处理如果统一参数使用规则的话,写法也会很明了。 但是,这也仅是编码规约而已,即使采用不同的写法也不会出错。
而Promise则是把类似的异步处理对象和处理规则进行规范化, 并按照采用统一的接口来编写,而采取规定方法之外的写法都会出错。
1.2 创建一个Promise对象
创建promise对象的流程如下所示。
1.new Promise(fn) 返回一个promise对象
2.在fn 中指定异步等处理
-处理结果正常的话,调用resolve(处理结果值)
-处理结果错误的话,调用reject(Error对象)
var promise = getAsyncPromise("fileA.txt");
promise.then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});
我们可以向这个预设了抽象化异步处理的promise对象, 注册这个promise对象执行成功时和失败时相应的回调函数。
这和回调函数方式相比有哪些不同之处呢? 在使用promise进行一步处理的时候,我们必须按照接口规定的方法编写处理代码。
也就是说,除promise对象规定的方法(这里的 then 或 catch)以外的方法都是不可以使用的, 而不会像回调函数方式那样可以自己自由的定义回调函数的参数,而必须严格遵守固定、统一的编程方式来编写代码。
这样,基于Promise的统一接口的做法, 就可以形成基于接口的各种各样的异步处理模式。
所以,promise的功能是可以将复杂的异步处理轻松地进行模式化, 这也可以说得上是使用promise的理由之一。
2. Promise的方法链
promise可以写成方法链的形式
aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
console.log(error);
});
如果把在 then 中注册的每个回调函数称为task的话,那么我们就可以通过Promise方法链方式来编写能以taskA → task B 这种流程进行处理的逻辑了。
如果我们将方法链的长度变得更长的话,那在每个promise对象中注册的onFulfilled和onRejected将会怎样执行呢?
看下面的promise chain:
···
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);
···
上面代码中的promise chain的执行流程,一张图描述就是这样:
promise-then-catch-flow.png
如果Task A 处理中发生异常的话,会按照TaskA → onRejected → FinalTask 这个流程来进行处理。(TaskB不会被调用)
2.1 Promise chain 如何传递参数
前面例子中的Task都是相互独立的,只是被简单调用而已。
这时候如果 Task A 想给 Task B 传递一个参数该怎么办呢?
答案非常简单,那就是在 Task A 中 return 的返回值,会在 Task B 执行时传给它。
我们还是先来看一个具体例子吧。
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执行流程如下所示:
1.Promise.resolve(1); 传递 1 给 increment 函数
2.函数 increment 对接收的参数进行 +1 操作并返回(通过return)
3.这时参数变为2,并再次传给 doubleUp 函数
4.最后在函数 output 中打印结果
每个方法中 return 的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。
return的值会由 Promise.resolve(return的返回值); 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的promise对象。
2.2 每次调用then都会返回一个新创建的promise对象
从代码上乍一看, aPromise.then(...).catch(...) 像是针对最初的 aPromise 对象进行了一连串的方法链调用。
然而实际上不管是 then 还是 catch 方法调用,都返回了一个新的promise对象。
下面我们就来看看如何确认这两个方法返回的到底是不是新的promise对象。
var aPromise = new Promise(function (resolve) {
resolve(100);
});
var thenPromise = aPromise.then(function (value) {
console.log(value);
});
var catchPromise = thenPromise.catch(function (error) {
console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(thenPromise !== catchPromise);// => true
===
是严格相等比较运算符,我们可以看出这三个对象都是互不相同的,这也就证明了 then 和 catch 都返回了和调用者不同的promise对象。
如果我们知道了 then 方法每次都会创建并返回一个新的promise对象的话,那么我们就应该不难理解下面代码中对 then 的使用方式上的差别了。
// 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 返回的值。
2.3 Promise.all
Promise.all
方法接受一个promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用.then
方法。
下面例子中, getURL 返回了一个promise对象,它封装了XHR通信的实现。 向 Promise.all 传递一个由封装了XHR通信的promise对象数组的话,则只有在全部的XHR通信完成之后(变为FulFilled或Rejected状态)之后,才会调用 .then 方法。
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);
});
在上面的代码中,request.comment()
和 request.people()
会同时开始执行,而且每个promise的结果(resolve或reject时传递的参数值),和传递给 [Promise.all
]的promise数组的顺序是一致的。
也就是说,这时候 .then
得到的promise数组的执行结果的顺序是固定的,即 [comment, people]。
2.4 Promise.race
它的使用方法和Promise.all一样,接收一个promise对象数组为参数。
Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理, 与之相对的是 Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。
网友评论