Promise学习(上):
资料: JavaScript Promise迷你书
原著:azu / 翻译:liubin、kaku、honnkyou Version 1.4.1 / http://liubin.org/promises-book/
概括笔记:
一、promise基本概念
1、Promise是抽象异步处理对象以及对其进行各种操作的组件。
2、Promise并不是从JavaScript中发祥的概念。Promise最初被提出是在E语言中, 它是基于并列/并行处理设计的一种编程语言。
3、Promise把类似的异步处理对象和处理规则进行规范化, 并按照采用统一的接口来编写,而采取规定方法之外的写法都会出错。
4、创建一个promise对象
从构造函数 Promise 来创建一个新建新promise对象作为接口。使用new来调用Promise的构造器来进行实例化promise对象。
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
5、promise.then() 实例回调方法
promise.then() 实例promise对象在 resolve(成功) / reject(失败)时调用的回调函数。promise.then(onFulfilled, onRejected)。
resolve(成功)时onFulfilled 会被调;reject(失败)时onRejected 会被调用;onFulfilled、onRejected 两个都为可选参数。
promise.then 成功和失败时都可以使用。 如果只想对异常进行处理则可以采用 promise.then(undefined, onRejected) 方式,只指定reject时的回调函数。 不过promise除了then,还有catch方法,捕获失败的处理。 promise.catch(onRejected) 。promise.catch(onRejected)。
6、简单的例子
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(value);
}, 16);
});
}
asyncFunction().then(function (value) {
console.log(value);
// => value
}).catch(function (error) {
console.log(error);
});
asyncFunction 函数返回promise对象, then 方法设置resolve后的回调函数, catch方法设置发生错误时的回调函数。在这种情况下 ,catch 的回调函数不会被执行(因为promise返回了resolve), 如果运行环境没有提供 setTimeout 函数,那么上面代码在执行中就会产生异常,在执行catch 中设置的回调函数。
* 不使用catch 方法:
我们可以使用promise.then(onFulfilled, onRejected) 方法声明,只使用 then方法。
asyncFunction().then(function (value) {
console.log(value);
},function (error) {
console.log(error);
});
7、实例化的promise对象的状态。
"has-resolution" - Fulfilled :
resolve(成功)时。此时会调用 onFulfilled。
"has-rejection" - Rejected :
reject(失败)时。此时会调用 onRejected。
"unresolved" - Pending :
既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等。
其中 左侧为在ES6 Promises规范中定义的术语, 而右侧则是在Promises/A+中描述状态的术语。
8、promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。在.then 后执行的函数只会被调用一次。Fulfilled和Rejected这两个中的任一状态都可以表示为Settled(不变的)。Settled 代表 resolve(成功) 或 reject(失败)。
* 关键点:当promise的对象状态发生变化时,用.then 来定义只会被调用一次的函数。
二、创建promise对象
1、创建promise对象的流程。
1.1、实例化promise对象:new Promise(fn) 返回一个promise对象
1.2、在fn 中指定异步等处理
处理结果错误的话,调用reject(Error对象)
处理结果正常的话,调用resolve(处理结果值)
2、为promise对象添加处理方法
promise对象的处理方法有两种:
promise对象被 resolve 时的处理(onFulfilled)
promise对象被 reject 时的处理(onRejected)
被resolve后的处理,可以在.then 方法中传入想要调用的函数。(如下面例子)getURL函数中的 resolve(req.responseText); 会将promise对象变为resolve(Fulfilled)状态, 同时使用其值调用 onFulfilled 函数。
在getURL 的处理中发生任何异常,或者被明确reject的情况下, 该异常原因(Error对象)会作为.catch方法的参数被调用。
例子: 用Promise来通过异步处理方式来获取XMLHttpRequest(XHR)的数据 :
声明一个getURL函数,返回一个promise实例包装XHR处理。
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){ // 被resolve后的处理,可以在.then方法中传入想要调用的函数。
console.log(value);
}).catch(function onRejected(error){ // 被reject后的处理,可以在.then 的第二个参数或者是在.catch方法中设置想要调用的函数。
console.error(error);
});
getURL 只有在通过XHR取得结果状态为200时才会调用 resolve -其他情况(取得失败)则调用 reject 方法。
XHR发生错误时 onerror 事件被触发,调用reject。发生错误时需要创建一个Error对象后再将具体的值传进去:reject(new Error(req.statusText)); 。
3、 Promise.resolve
new Promise() 方法的快捷方式:静态方法Promise.resolve(value)。
例如:
基本的用法:
new Promise(function(resolve){
resolve(42); //resolve(42); 会让promise对象立即进入确定(resolved)状态,并将 value值42 传递给后面then里所指定的 onFulfilled 函数
});
语法糖:
Promise.resolve(42);
Promise.resolve(value) 方法的返回值是一个promise对象,可以对其返回值进行 .then 调用。
Promise.resolve(42).then(function(value){
console.log(value);
});
Promise.resolve 方法的作用就是将传递给它的参数填充(Fulfilled)到promise对象后并返回这个promise对象。
3.1、Promise.resolve 方法另一个作用就是将thenable对象转换为promise对象
(thenable指的是一个具有 .then 方法的对象。例如jQuery.ajax())。thenable对象可以使用 Promise.resolve 来转换为一个promise对象。就能直接使用 then 或者 catch 等在ES6 Promises里定义的方法。
3.1.1、将thenable对象转换promise对象
var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
console.log(value);
});
*thenable 对象我们一般用不到
4、 Promise.reject
Promise.reject(error)是和Promise.resolve(value)类似的静态方法,也是 new Promise() 方法的快捷方式。
常规写法是:
new Promise(function(resolve,reject){
reject(new Error("error"));
}); //调用该promise对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给 onRejected 函数。
语法糖:
Promise.reject(new Error("error")).catch(function(error){
console.error(error);
});
5、promise异步操作
1、一般的使用情况下,接收回调函数的函数,根据具体的执行情况,可以选择是以同步还是异步的方式对回调函数进行调用。
异步回调函数同步调用 ?
NO!!!
1、绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
2、如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。
3、对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。
4、如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。
例子:
这个函数会接收一个回调函数进行处理。
function onReady(fn) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
fn();
} else {
window.addEventListener('DOMContentLoaded', fn);
}
}
onReady(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
如果在调用onReady之前DOM已经载入的话
如果在调用onReady之前DOM还没有载入的话
对回调函数进行同步调用
通过注册 DOMContentLoaded 事件监听器来对回调函数进行异步调用
因此,如果这段代码在源文件中出现的位置不同,在控制台上打印的log消息顺序也会不同。为了解决这个问题,我们可以选择统一使用异步调用的方式。
例子2:
setTimeout 等异步API异步调用回调函数:
function onReady(fn) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
setTimeout(fn, 0);
} else {
window.addEventListener('DOMContentLoaded', fn);
}
}
onReady(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
为了避免上述中同时使用同步、异步调用可能引起的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式 。
例子三:
promise重写上述onReady函数:
function onReadyPromise() {
return new Promise(function (resolve, reject) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
resolve();
} else {
window.addEventListener('DOMContentLoaded', resolve);
}
});
}
onReadyPromise().then(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
Promise能保证每次调用都是以异步方式进行。
6、Promise 方法链method 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);
执行流程:
6.1、promise chain 中如何传递参数
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.resolve(1); 传递 1 给 increment 函数;函数 increment 对接收的参数进行 +1 操作并返回结果,接着传给 doubleUp 函数;最后在函数 output 中打印结果。
* return的值会由 Promise.resolve(return的返回值); 进行相应的包装处理,最终 then 的结果返回一个新创建的promise对象。Promise#then 除了注册一个回调函数,还将回调函数的返回值进行变换,创建并返回一个promise对象。
特别注意的地方:then 返回返回新创建的promise对象。
例子:✘ then 的错误使用方法
function badAsyncCall() {
var promise = Promise.resolve();
promise.then(function() {
// 任意处理
return newVar;
});
return promise;
}
正确方法:then 返回返回新创建的promise对象
function anAsyncCall() {
var promise = Promise.resolve();
return promise.then(function() {
// 任意处理
return newVar;
});
}
这种函数的行为贯穿在Promise整体之中,接收一个promise对象为参数,并返回一个和接收参数不同的、新的promise对象。
7、 使用Promise#then同时处理多个异步请求
7.1、Promise.all
Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .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数组的顺序一致。
7.2、Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理。
与之相对的是 Promise.race 。只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。
暂时学习到前两章,东西太多,一时间消化不了。