前言
Promise, 用于解决回调地狱带来的问题, 将异步操作以同步的操作编程表达出来, 避免了层层嵌套的回调函数.
既然是用来解决回调地狱的问题, 那首先来看下什么是回调地狱
- var sayhello = function(callback){
- setTimeout(function(){
- console.log("hello");
- return callback(null);
- },1000);
- }
- sayhello(function(err){
- console.log("xiaomi");
- });
- console.log("mobile phone");
输出结果
- mobile phone
- hello
- Xiaomi
看上面这段代码, 假如我们需要对输出内容的顺序进行调整, 例如依次打印 xiaomi apple huawei , 那么我们之前的做法是怎么样的
- var sayhello = function(name, callback){
- setTimeout(function(){
- console.log("hello");
- console.log(name);
- return callback(null);
- },1000);
- }
- sayhello("xiaomi", function(err){
- sayhello("apple", function(err){
- sayhello("huawei", function(err){
- console.log("end");
- });
- });
- });
- console.log("mobile phone");
问题很明显, 代码层层嵌套, 看起来十分的混乱, 如果层级代码更多更是难以维护
因此 Promise 的出现使得我们可以用同步的方式来操作异步代码, 解决以上问题
初识 promise
- var getUserInfo = function() {
- return new Promise(function(resolve) {
- setTimeout(function() {
- var user = {name: 'Kerry Wu', age: 31};
- resolve(user);
- }, 3000);
- });
- };
- this.getUserInfo().then(function(userInfo) {
- console.log('userInfo',userInfo);//{ name: 'Hanmeimei', age: 31 }
- });
我们通过 new 关键词实例化一个 Promise 对象并返回该对象, 然后使用. then 的形式获取 Promise 返回的内容
这里需要注意的是, new Promise 实例化是一个同步的过程, 而. then 是一个异步的过程, 关于同步异步执行顺序 , 先执行同步在执行异步代码
Promise 状态
1,Pending 进行中 / Resolved 已成功 / Rejected 已失败
resove 将未完成变成已完成 pending => resolved
reject 将未完成变成已失败 pending => rejected
- var promise = new Promise(function(resolve, reject) {
- // ... some code
- if (/* 异步操作成功 */){
- resolve(value);
- } else {
- reject(error);
- }
- });
2,then 与 catch
then 方法接收两个函数参数, 第一个表示 resove 已成功的回调, 第二个表示 reject 已失败的回调
用法如下:
- var p = new Promise(function(resolve, reject){
- ...
- })
- p.then(function(){
- }, function(){
- })
- p.then().catch();
then, 前一个 then 的返回结果, 可以再后一 then 的回调中获取, 如:
- var p3 = ()=> new Promise((resolve, reject)=>{
- resolve('{"name":"jack","age":28}')
- });
- p3()
- .then(res => JSON.parse(res))
- .then(data => Object.assign(data, {name:'rose'}))
- .then(data => console.log(data))
- // 输出:{name: "rose", age: 28}
异步加载图片
- function loadImageAsync(url) {
- return new Promise(function(resolve, reject) {
- var image = new Image();
- image.onload = function() {
- resolve(image);
- };
- image.onerror = function() {
- reject(new Error('Could not load image at' + url));
- };
- image.src = url;
- });
- }
- loadImagesAsync('//img.static.com/xxx.jpg').then(function(img){
- // 加载成功 显示图片
- }, function(err){
- // 加载失败 提示失败
- })
异步加载数据
使用 promise 包装一个异步请, 返回一个 promise 对象, 使用 then 和 catch 的方式对返回结果进行处理
- var getJSON = function(url){
- return new Promise((resolve, reject)=>{
- var client = new XMLHttpRequest();
- client.open('GET', url);
- client.onreadystatechange = callback;
- client.send();
- function callback(){
- if(this.readyState !== 4) return;
- if(this.status === 200){
- resolve(this.response)
- }else{
- reject(new Error(this.statusText))
- }
- }
- })
- }
- getJSON('/api/getList').then(function(data){
- // 获取请求的数据
- }, function(err){
- // 请求失败错误处理
- });
- catch
p.catch()用于处理 promise 中 rejected 状态的回调, 与 p.then(resolveFn, rejectFn)中 rejectFn 的作用相同
- var p = new Promise(function(resolve, reject){
- ...
- });
- p.then(function(){
- }, function(){
- });
- // 等同于
- p.then(function(){
- }).catch(function(){
- });
reject('error') 与 throw new Error('...') 都能被 catch 捕获
- new Promise((resolve, reject) => {
- throw new Error('some error1');
- }).catch(err => console.log(err.message))
- // 等同于
- new Promise((resolve, reject) => {
- reject('some error2')
- }).catch(err => console.log(err))
捕获异常
promise 对象的错误, 具有 冒泡 性质, 会一直向后传递, 直到被捕获
推荐使用 catch 代替 then(null, rejectFn)中的 rejectFn,catch 可以捕获前面 then 函数返回的错误信息, 也更接近同步的写法
- // bad
- new Promise(function(resolve, reject){
- }).then(resolveFn, rejectFn)
- // good
- new Promise(function(resolve, reject){
- }).then(resoveFn).catch(rejectFn)
Promise all 与 race
Promise.all([]) 与 Promise.race([])
接收一个数组做为参数, 参数中的每个元素为 promise 实例,
如果元素不是 promise 实例, 则会调用 Promise.resolve()转换为 promise 的实例
将多个 promise 对象包装为一个新的 promise 对象
1,Promise.all()
Promise 的 all 方法提供了并行执行异步操作的能力, 并且在所有异步操作执行完后才执行回调
当 p1,p2,p3 的状态全部为 resolved 时, 才能将 p 的状态改为 resolved
当 p1,p2,p3 其中一个状态变成 rejected 时, 就会将 p 的状态变成 rejected
- var p = Promise.all([Promise.resolve('1'), Promise.resolve('2'), Promise.resolve('3')]);
- p.then(data => console.log(data)) //["1", "2", "3"]
- var p1 = Promise.all([Promise.resolve('1'), Promise.reject('2'), Promise.resolve('3')]);
- p1.then(data => console.log(data)).catch(err => console.log(err)) // 2
Promise.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();
- });
- }
- function getComment() {
- return getURL('http://azu.github.io/promises-book/json/comment.json').then(res=>JSON.parse(res));
- }
- function getPeople() {
- return getURL('http://azu.github.io/promises-book/json/people.json').then(res=>JSON.parse(res));
- }
- // 合并请求
- Promise.all([getComment(), getPeople()]).then(function (value) {
- console.log(value);
- }).catch(function(error){
- console.log(error);
- });
Promise resolve 和 reject
Promise.resolve() 与 Promise.reject()
- Promise.resolve('foo')
- // 等价于
- new Promise(resolve => resolve('foo'))
最后
记得以前在面试的时候, 被问了一道很有意思的面试题, 主要是考察 promise 和 settimeout 执行顺序
- setTimeout(function () {
- console.log(1)
- }, 0);
- new Promise(function executor(resolve) {
- resolve();
- }).then(function () {
- console.log(2);
- });
如上代码, 为什么运行结果是 2,1 而不是 1,2?
不是 setTimeout 先加入任务队列吗?
解答:
1, 从规范上来讲, setTimeout 有一个 4ms 的最短时间, 也就是说不管你设定多少, 反正最少都要间隔 4ms 才运行里面的回调(当然, 浏览器有没有遵守这个规范是另外一回事儿). 而 Promise 的异步没有这个问题.
2, 从具体实现上来说, 这俩的异步队列不一样, Promise 所在的那个异步队列优先级要高一些.
还有一道差不多的
- (function test() {
- setTimeout(function() {console.log(4)}, 0);
- new Promise(function executor(resolve) {
- console.log(1);
- for( var i=0 ; i<10000 ; i++ ) {
- i == 9999 && resolve();
- }
- console.log(2);
- }).then(function() {
- console.log(5);
- });
- console.log(3);
- })()
为什么输出结果是 1,2,3,5,4 而非 1,2,3,4,5 ?
解答:
1,Promise.then 是异步执行的, 而创建 Promise 实例 ( executor ) 是同步执行的.
2,setTimeout 的异步和 Promise.then 的异步不太一样不在同一个队列中, setTimeout(fn, 0)在下一轮 "事件循环" 开始时执行, Promise.then()在本轮 "事件循环" 结束时执行. 因此 then 函数先输出, settimeout 后输出.
这里涉及到 JS 事件循环, 任务队列的东西, 了解更多 https://www.cnblogs.com/hity-tt/p/6733062.html
参考阅读
http://coderlt.coding.me/2016/07/17/ES6-promise/
来源: https://www.cnblogs.com/fozero/p/10423830.html