在前端开发中,有个很熟悉的词叫做“回调”,在处理一些异步的函数的时候,回调被广泛应用,但是大量用回调来编程,会出现嵌套层级过多,代码风格不规范,不清晰的问题。“Promise/A+规范”是一种很方便的异步编程方式。
使用promise进行编程有哪些好处?
- 将复杂的异步处理轻松地进行模式化
- 代码更清晰
- 异常处理更方便
- 代码链式操作,爽!
先来一段简单的promise的代码:
var promise = getAsyncPromise("fileA.txt");
promise.then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});
通过这个代码,是不是觉得promise异步编程很清晰?
下面我详细介绍下promise实用的方法。
Promise 简介
1. 构建promise对象
- Promise类似于 XMLHttpRequest ,从构造函数 Promise 来创建一个新建新 promise 对 象作为接口。
- 要想创建一个promise对象、可以使用 new 来调用 Promise 的构造器来进行实例化。如下:
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
2. 常用的方法
- 对通过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. 静态方法
- 包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的 辅助方法。
4. 来个栗子
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 调用返回值时的回调函数。
- 这个函数会返回promise对象,对于这个promise对象,我们调用的 方法来设置resolve后的回调函数,catch方法来设置发生错误时的回调函数。
- 在这种情况下catch的回调函数并不会被执行(因为promise返回了resolve), 不过如果运行环境没有提供setTimeout函数的话,那么上面代码在执行中就会产生异常,在catch中设置的回调函数就会被执行。
上面的代码也也可以不用catch方法,用then(resolve, reject)的形式(链式操作时最好用catch,可以统一捕捉异常):
asyncFunction().then(function (value) { console.log(value);
}, function (error) { console.log(error);
});
Promise 状态详解
大致的了解promise处理流程,单独介绍一下promise状态
1. 大致三个状态
- resolve(成功)时。此时会调用 onFulfilled
- reject(失败)时。此时会调用 onRejected
- 既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等
*注意:从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状 态就不会再发生任何变化。
当promise的对象状态发生变化时,用 .then 来定义只会被调用一次的函数。
编写Promise代码:
1. 创建promise对象
- new Promise(fn) 返回一个promise对象
- 在 fn 中指定异步等处理 • 处理结果正常的话,调用 resolve(处理结果值) • 处理结果错误的话,调用 reject(Error对象)
来个XHR的promise对象的栗子
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest(); //栗子就不做ie兼容了
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); });
栗子说明:
- getURL 只有在通过XHR取得结果状态为200时才会调用 resolve - 也就是只有数据取 得成功时,而其他情况(取得失败)时则会调用 reject 方法。
- resolve(req.responseText) 在response的内容中加入了参数。 resolve方法的参数并没有特 别的规则,基本上把要传给回调函数参数放进去就可以了。 ( then 方法可以接收到这 个参数值)
6. 编写promise对象处理方法
让我们在实际中使用一下刚才创建的返回promise对象的函数
getURL("http://example.com/"); // => 返回promise对象
为promise对象添加处理方法主要有以下两种:
- promise对象被 resolve 时的处理(onFulfilled)
- promise对象被 reject时的处理(onRejected)
getURL(URL).then(onFulfilled, onRejected); //纯then写法
getURL(URL).then( onFulfilled).catch( onRejected); // then chatch写法
注:一般说来,使用 .catch 来将resolve和reject处理分开来写是比较推荐的做法, 这两者的 区别会在then和catch的区别中再做详细介绍。
详解promise
1. 使用 Promise.resolve
- Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
//比如 Promise.resolve(42); 可以认为是以下代码的语法糖。
new Promise(function(resolve){
resolve(42);
});
在这段代码中的 resolve(42);会让这个promise对象立即进入确定(即resolved)状态,并将 42 传递给后面then里所指定的 onFulfilled 函数。
Promise.resolve(42).then(
function (value) {
console.log(value) //42
}
);
注:Promise.resolve作为newPromise()的快捷方式,在进行promise对象的初始化或者编写 测试代码的时候都非常方便。
-
Promise.resolve方法另一个作用就是将thenable对象转换为promise对象。
**thenable对象**:简单来说它就是一个非常类似promise的东西(*就像我们有时称具有.length方法的非数组对象为Arraylike一样,thenable指的是一个具有.then 方法的对象*。)
-
最简单的例子就是jQuery.ajax(),它的返回值就是thenable的(因为 jQuery.ajax()的返回值是jqXHRObject对象,这个对象具有 .then 方法)
-
用Promise.resolve来转换为一个promise对象。变成了promse对象的话,就能直接使用 then 或者 catch等这些在ES6Promises里定 义的方法了。
-
var promise = Promise.resolve(
$.ajax('/json/comment.json')
) // => promise对象
promise.then(function(value){
console.log(value);
});
总结:简单总结一下 Promise.resolve 方法的话,可以认为它的作用就是将传递给它的参数填 充(Fulfilled)到promise对象后并返回这个promise对象。
2. 使用 Promise.reject
- Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方 法的快捷方式。
new Promise(
function (resolve,reject) {
reject(new Error("出错了"));
}
);
// 是Promise.reject(newError("出错了"))的语法糖。
//如下:
Promise.reject(new Error("BOOM!")).catch(
function(error){
console.error(error);
}
);
上一篇写了一部分,再写一些关于promise的笔记
Promise.then() &&链式写法
- promise可以写成方法链的形式
aPromise.then(function taskA(value) {
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){ //最后catch 统一捕捉异常
console.log(error);
});
来段代码,看下链式操作的执行流程
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);
上面这段代码的执行流程如下图:
注释: .cntch() 可以捕捉 taskA taskB出现的异常,但是 onRejected、finalTask这两个后面没有catch捕捉了,所以这两个函数出现问题不会被捕捉到。
- 用then、catch 链式写法,要比纯then写法好!!
- catch 可以捕捉到resolve函数里面的错误,有时候我可以主动在resolve函数里throw一个错误或者返回一个Rejected状态的promise对象(推荐后者);
- 链式操作代码更简洁
Promise链式写法中参数如何传递
前面例子中的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);
}
var promise = Promise.resolve(1);
promise
.then(increment)
.then(doubleUp)
.then(output)
.catch(function(error){
// promise chain中出现异常的时候会被调用
console.error(error);
});
1. Promise.resolve(1); 传递 1 给 increment 函数
2. 函数 increment 对接收的参数进行 +1 操作并返回(通过 return ) 3. 这时参数变为2,并再次传给 doubleUp 函数
3. 最后在函数 output 中打印结果
**注:**每个方法中return的值不仅只局限于字符串或者数值类型,也可以是对象或者promise 对象等复杂类型。
- return的值会由Promise.resolve进行相应的包装处理,因此不回调函数中会返回一个什么样的值,最终的结果都是返回一个新创建的promise对象。
Promise.catch 的一些小问题
- IE8 兼容问题catch是ECMAScrip3中保留字是不能作为对象的属性名使用的(ie8大概是基于es3实现的)可以用Promise['catch']来调用(不过很多压缩、打包工具自带转换功能,还不错)
Promise处理多个异步任务,promise.all 上场
Promise.all 接收一个promise对象的数组作为参数,当这个数组里的所有promise对象 全部变为resolve或reject状态的时候,它才会去调用 .then 方法。
// `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]
}
);
**注:**从总用时120ms来看,传递给Promise.all的promise并不是一个个的顺序执行的,而是 同时开始、并行执行的。
Promise处理多个异步任务,Promise.race 上场
它的使用方法和Promise.all一样,接收一个promise对象数组为参数
Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理,与之相对的是Promise.race 只要有一个promise对象进入FulFilled或者Rejected状态的话,就会继续进行后面的处理。
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1 只打印出一个最先出结果的
});