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 迷你书,想更加深入的同学请移步这里。
javaScript Promise 入门的更多相关文章
-
Javascript Promise入门
是什么? https://www.promisejs.org/ What is a promise? The core idea behind promises is that a promise r ...
-
JavaScript Promise:去而复返
原文:http://www.html5rocks.com/en/tutorials/es6/promises/ 作者:Jake Archibald 翻译:Amio 女士们先生们,请准备好迎接 Web ...
-
JavaScript从入门到精通(转)
JavaScript从入门到精通 转自: https://github.com/Eished/JavaScript_notes 视频连接:https://www.bilibili.com/video/ ...
-
Promise入门到精通(初级篇)-附代码详细讲解
Promise入门到精通(初级篇)-附代码详细讲解 Promise,中文翻译为承诺,约定,契约,从字面意思来看,这应该是类似某种协议,规定了什么事件发生的条件和触发方法. Pr ...
-
promise入门基本使用
Promise入门详解和基本用法 异步调用 异步 JavaScript的执行环境是单线程. 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任 ...
-
【转】HTML, CSS和Javascript调试入门
转 http://www.cnblogs.com/PurpleTide/archive/2011/11/25/2262269.html HTML, CSS和Javascript调试入门 本文介绍一些入 ...
-
[Javascript] Promise
Promise 代表着一个异步操作,这个异步操作现在尚未完成,但在将来某刻会被完成. Promise 有三种状态 pending : 初始的状态,尚未知道结果 fulfilled : 代表操作成功 r ...
-
Javascript Promise 学习笔记
1. 定义:Promise是抽象异步处理对象以及对其进行各种操作的组件,它把异步处理对象和异步处理规则采用统一的接口进行规范化. 2. ES6 Promises 标准中定义的API: ...
-
JavaScript快速入门(四)——JavaScript函数
函数声明 之前说的三种函数声明中(参见JavaScript快速入门(二)——JavaScript变量),使用Function构造函数的声明方法比较少见,我们暂时不提.function func() { ...
随机推荐
-
Python强化训练笔记(六)——让字典保持有序性
python的字典是一个非常方便的数据结构,使用它我们可以轻易的根据姓名(键)来找到他的成绩,排名等(值),而不用去遍历整个数据集. 例如:{'Lee': [1, 100], 'Jane': [2, ...
-
C++链接两个cpp 文件
我们在编程中,有没有想过,分别写代码,然后把两个cpp,文件合并,两个自身本不能运行的文件,在一起却可以运行(主要牵扯函数调用,一个有声明和调用,另一个定义).那么具体如何实现呢? 跟着我的步骤: 1 ...
-
下拉更新列表Android-PullToRefresh
项目地址:https://github.com/chrisbanes/Android-PullToRefresh
-
李洪强iOS学习交流群-iOS大神群
iOS学习大神群-群号:483959373
-
linux page cache和buffer cache
主要区别是,buffer cache缓存元信息,page cache缓存文件数据 buffer 与 cache 是作为磁盘文件缓存(磁盘高速缓存disk cache)来使用,主要目的提高文件系统系性能 ...
-
[PGM] Exact Inference for calculating marginal distribution
如何在贝叶斯网络中求解某变量的边缘分布? 这是一个问题. 贝叶斯网络如下: CPTs如下: (1) How to compute p( L | C = high )? p( L | C = high ...
-
Android 7.0 适配
extends:http://www.jianshu.com/p/56b9fb319310http://blog.csdn.net/chay_chan/article/details/57083383
- ado.net调用带参数的sql语句
-
ARMV8 datasheet学习笔记4:AArch64系统级体系结构之编程模型(3)- 异常
1.前言 本文介绍异常相关内容,包括异常类型,异常进入,异常返回,异常层次结构,异常的路由等 2. RESET ARMV8体系结构支持两种类型的RESET Cold reset:Reset PE所有 ...
-
C++笔记:头文件的作用和写法
from://http://ceeji.net/blog/c%E7%AC%94%E8%AE%B0%EF%BC%9A%E5%A4%B4%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD% ...