目录
2.1.3 then 方法返回的 Promise 对象的状态改变
3.1 promise.resolve()方法和promise.reject()方法
4.1.1 resolve 或 reject 函数执行后的代码
4.1.2 Promise.all/race/allSettled 的参数问题
4.1.3 Promise.all/race/allSettled 的错误处理
一、初识promise
1.1 什么是promise
- 异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
- promise一般是用来解决层层嵌套的回调函数的问题(回调地狱)
- 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
- 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
- 提供统一的 API,各种异步操作都可以用同样的方法进行处理
示例:回调地狱(了解)
<div id="box"></div>
<style>
* {
padding: 0;
margin: 0;
}
#box {
width: 300px;
height: 300px;
background-color: red;
transition: all 0.5s;
}
</style>
<script>
const move = (el, {
x = 0,
y = 0
} = {}, end = () => {}) => {
el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
el.addEventListener(
'transitionend',
() => {
// console.log('end');
end();
},
false
);
};
const boxEl = document.getElementById('box');
document.addEventListener(
'click',
() => {
move(boxEl, {
x: 150
}, () => {
move(boxEl, {
x: 150,
y: 150
}, () => {
move(boxEl, {
y: 150
}, () => {
// console.log('object');
move(boxEl, {
x: 0,
y: 0
});
});
});
});
},
false
);
</script>
1.2 Promise对象的特点
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果
1.3 Promise的缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择
1.4 promise的基本用法
- 实例化构造函数生成实例对象
- promise的3种状态
- then()方法
- resolve()和reject()函数的参数
promise对象本身不是异步的,只要创建了promise对象就会立即执行存放的代码
a.实例化构造函数生成实例对象:
console.log(Promise);
//Promise 解决的不是回调函数,而是回调地狱
const p = new Promise(() => {
function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
[点击并拖拽以移动]
});
解释:
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
b.Promise 的状态:
const p = new Promise((resolve, reject) => {
// Promise 有 3 种状态,
//一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功
// 执行 reject,变成 rejected,已失败
// Promise 的状态一旦变化,就不会再改变了
// pending->fulfilled
resolve(); //这里由于resolve()在前面,因此在此时状态就是fulfilled,就不会再改变,即使下面还会再执行reject(0
// pending->rejected
reject();
});
console.log(p);
1.5 then 方法
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数,即then方法接收两个参数:
第一个参数是状态切换成功时的回调
第二个参数是切换失败时的回调
示例:
const p = new Promise((resolve, reject) => {
resolve(); // 将状态切换为成功
// reject(); //将状态切换为失败
});
console.log(p);
p.then(
() => { // 成功时执行
console.log('success');
},
() => { // 失败时执行
console.log('error');
}
);
1.6 resolve 和 reject 函数的参数
在修改promise状态时,可以传递参数给then()中的回调函数
const p = new Promise((resolve, reject) => {
// pending->fulfilled
// resolve('succ');
resolve({
username: 'alex'
});
// pending->rejected
// reject('reason');
// reject(new Error('reason'));
});
p.then(
data => {
console.log('success', data);
},
err => {
console.log('error', err);
}
);
console.log(p);
注意:其状态只能是pending(未完成)->fulfilled成功()或者pending(未完成)->rejected(失败)
二、promise的实例方法
2.1 then()方法
2.1.1 什么时候执行
pending->fulfilled 时,执行 then 的第一个回调函数
pending->rejected 时,执行 then 的第二个回调函数
2.1.2 执行后的返回值
同一个promise对象可以多次调用then(),当该promise对象的状态改变时所有then()都会执行
then 方法执行后返回一个新的 Promise 对象
//then 方法执行后返回一个新的 Promise 对象
const p = new Promise((resolve, reject) => {
resolve();
//reject();
});
const p2 = p
.then(
() => {},//成功后执行
() => {} //失败后执行
)
.then()
.then();
console.log(p, p2, p === p2);
2.1.3 then 方法返回的 Promise 对象的状态改变
同一个promise对象可以多次调用then(),当该promise对象的状态改变时所有then()都会执行
then 方法每次执行完后都会返回一个新的 Promise 对象
可以通过上一个promise对象的then()给下一个promise对象的then()传递参数,需要注意的是无论是在上一个promise对象成功的回调还是失败的回调传递的参数,都会传递给下一个promise对象成功的回调
//then 方法执行后返回一个新的 Promise 对象
const p = new Promise((resolve, reject) => {
// resolve();
reject();
});
p.then(
() => { //成功之后执行的
console.log('success');
},
() => { //失败之后执行的
console.log('err');
//利用return进行给下一个promise对象的then()传参
//在then()的回调函数中,return后面的东西,会自动用Promise包装一下
//return undefined;
// 等价于
// return new Promise((resolve, reject) => {
// resolve(123);
// });
//默认返回的永远都是成功状态的Promise对象
//若想返回失败则:
return new Promise((resolve, reject) => {
reject('reason');
});
}
).then((data) => {
// 前一个then()执行后,返回的心的promise对象才能决定后一个promise对象
console.log('success2', data);
}, (err) => {
console.log('error2');
}).then((data) => {
//由于默认返回的永远都是成功状态的Promise对象,因此在这里会执行data,而不是err,
//要想执行err,就必须向上面那样进行修改
console.log('success3', data);
}, ((err) => {
console.log('err3', err);
}));
2.2 catch()方法
catch()其实是then()的语法糖
- catch 专门用来处理 rejected 状态
- catch 本质上是 then 的特例
- catch() 可以捕获它前面的错误
- 一般总是建议,Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误
2.2.1 catch()特点
- 和then()一样,在修改promise状态时,可以传递参数给catch()中的回调函数
let p = new Promise((resolve, reject) => {
reject('123');
});
p.catch(data => {
console.log(data); //123
})
- 和then()一样,同一个promise对象可以多次调用catch(),当该promise对象的状态发生改变时所有catch()都会执行
let p = new Promise((resolve, reject) => {
reject();
});
p.catch(() => {
console.log('失败1'); //
})
p.catch(() => {
console.log('失败2'); //
})
p.catch(() => {
console.log('失败3'); //
})
- 和then()一样,catch()每次执行完毕后会返回一个新的promise对象
let p = new Promise((resolve, reject) => {
reject();
});
let p1 = p.catch(() => {
console.log('失败1'); //
});
console.log(p === p1); //false
- 和then()一样,上一个promise对象也可以给下一个promise成功的传递参数,需要注意的是无论是在上一个promise对象成功的回调还是失败的回调传递的参数,都会传递给下一个promise对象成功的回调
let p = new Promise((resolve, reject) => {
reject();
});
let p1 = p.catch(() => {
console.log('失败1'); //
return '789';
});
p1.then((data) => {
console.log('成功2');
}, (data) => {
console.log('失败2');
});
- 和then()一样,catch()如果返回的是一个promise对象,那么会将返回的promise对象的执行结果中的值传递给下一个catch()
let p = new Promise((resolve, reject) => {
reject();
});
let pp = new Promise((resolve, reject) => {
resolve('111');
// reject('abc');
});
let p1 = p.catch(() => {
console.log('失败1'); //
return pp;
});
p1.then((data) => {
console.log('成功2');
}, (data) => {
console.log('失败2');
});
- 和then()第二个参数的区别在于,catch()可以捕获上一个promise对象then()中的异常
let p = new Promise((resolve, reject) => {
resolve();
});
p.then(() => {
console.log('成功');
xxx
}).catch((e) => {
console.log('失败', e);
});
2.2.2 基本用法
<script>
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
})
.then(data => {
console.log(data);
})
// .then(null, err => {
// console.log(err);
// });
.catch(err => {
console.log(err);
// return undefined;默认情况下总是执行resolve(),返回成功状态的
throw new Error('reason'); //直接抛出错误,改变状态
})
.then(data => {
console.log(data);
})
.catch(err => {
console.log(err);
});
</script>
2.3 finally()方法(了解)
2.3.1 什么时候执行
当 Promise 状态发生变化时,不论如何变化都会执行,不变化不执行
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
})
.finally(data => {
console.log(data);
})
.catch(err => {});
2.3.2 本质
finally() 本质上是 then() 的特例
// new Promise((resolve, reject) => {
// // resolve(123);
// reject('reason');
// })
// .finally(data => {
// console.log(data);
// })
// .catch(err => {});
// 等同于
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
})
.then(
result => {
return result;
},
err => {
return new Promise((resolve, reject) => {
reject(err);
});
}
)
.then(data => {
console.log(data);
})
.catch(err => {
console.log(err);
});
三、promise的构造函数方法
3.1 promise.resolve()方法和promise.reject()方法
3.1.1 Promise.resolve()
是成功状态 Promise 的一种简写形式
new Promise(resolve => resolve('foo'));
简写
Promise.resolve('foo');
参数:
一般参数:
<script>
Promise.resolve('foo').then(data => {
console.log(data);
});
</script>
特殊参数:
1.Promise
当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做
<script>
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
// setTimeout(() => {
// resolve('我执行了');
// }, 1000);
});
Promise.resolve(p1).then(data => {
console.log(data);
});
// 等价于
p1.then(data => {
console.log(data);
});
console.log(Promise.resolve(p1) === p1);
</script>
当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调
<script>
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
// setTimeout(() => {
// resolve('我执行了');
// }, 1000);
});
new Promise(resolve => resolve(p1)).then(data => {
console.log(data);
});
</script>
2.具有 then 方法的对象
<script>
const thenable = {
then(resolve, reject) {
console.log('then');
resolve('data');
// reject('reason');
}
};
Promise.resolve(thenable).then(
data => console.log(data),
err => console.log(err)
);
console.log(Promise.resolve(thenable));
</script>
3.1.2 Promise.reject()
失败状态 Promise 的一种简写形式
new Promise((resolve, reject) => {
reject('reason');
});
等价于
Promise.reject('reason');
参数
不管什么参数,都会原封不动地向后传递,作为后续方法的参数
<script>
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
});
Promise.reject(p1).catch(err => console.log(err));
</script>
示例2:
<script>
new Promise((resolve, rejcet) => {
resolve(123);
})
.then(data => {
// return data;
// return Promise.resolve(data);
return Promise.reject('reason');
})
.then(data => {
console.log(data);
})
.catch(err => console.log(err));
</script>
3.2 promise.all()方法
a.有什么用
- Promise.all() 关注多个 Promise 对象的状态变化
- 传入多个 Promise 实例,包装成一个新的 Promise 实例返回
b.基本用法
- Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关
- 所有状态都变成 resolved,最终的状态才会变成 resolved
- 只要有一个变成 rejected,最终的状态就变成 rejected
c.特点
会返回一个新的promise对象
会按照传入数组的顺序将所有promise中成功返回的结果保存到一个新的数组中返回
数组中有一个promise失败就会失败,只有所有成功才会成功
<script>
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
// return 'p1';
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
// return Promise.reject('reason');
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
</script>
3.3 promise.race()方法
Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败
<script>
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
// return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
// return 'p2';
return Promise.reject('reason');
});
const racePromise = Promise.race([p1, p2]);
racePromise.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
</script>
3.4 promise.allSettled()方法
Promise.allSettled() 的状态与传入的Promise 状态无关
永远都是成功的
它只会忠实的记录下各个 Promise 的表现
3.4.1 特点
接收一个数组
如果数组中有多个promise对象,谁先返回状态就听谁的,后返回的就会被抛弃
如果数组中不是promise对象,那么会直接执行then()
<script>
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
// return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
// return 'p2';
return Promise.reject('reason');
});
const allSettledPromise = Promise.allSettled([p1, p2]);
allSettledPromise.then(data => {
console.log('succ', data);
});
</script>
3.4.2 应用场景
接口调试、超时处理
let url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp01%2F1ZZQ20QJS6-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639382820&t=005df0df320dfe0d7a43cfa605b65bca";
function loadImg(url) {
return new Promise((resolve, reject) => {
//设置图片地址
let oImg = new Image();
setTimeout(function() {
oImg.src = url;
}, 5000);
//图片加载成功
oImg.onload = () => {
resolve(oImg);
};
//图片加载失败
oImg.onerror = () => {
reject("图片加载失败");
};
});
}
function timeOut() {
return new Promise((resolve, reject) => {
setTimeout(function() {
reject("超时了");
}, 3000)
});
}
Promise.race([loadImg(url), timeOut()]).then((value) => {
console.log("成功", value);
}).catch(error => {
console.log("失败", error);
});
四、promise的注意事项和应用
4.1 promise的注意事项
4.1.1 resolve 或 reject 函数执行后的代码
推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行它们后面的代码
<script>
new Promise((resolve, reject) => {
// return resolve(123);
return reject('reason');
console.log('hi');
});
</script>
4.1.2 Promise.all/race/allSettled 的参数问题
参数如果不是 Promise 数组,会将不是 Promise 的数组元素转变成 Promise 对象
<script>
Promise.all([1, 2, 3]).then(datas => {
console.log(datas);
});
//等价于
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(datas => {
console.log(datas);
});
</script>
不只是数组,任何可遍历的都可以作为参数:数组、字符串、Set、Map、NodeList、arguments
<script>
Promise.all(new Set([1, 2, 3])).then(datas => {
console.log(datas);
});
</script>
4.1.3 Promise.all/race/allSettled 的错误处理
错误既可以单独处理,也可以统一处理
一旦被处理,就不会在其他地方再处理一遍
<script>
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
// return 'p1';
return Promise.reject('reason');
});
//方式1:单独处理
// .catch(err => {
// console.log('p1', err);
// });
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
// return Promise.reject('reason');
});
//方式1:单独处理
// // .catch(err => {
// // console.log('p2', err);
// });
const allPromise = Promise.all([p1, p2]);
//方式2:统一处理
allPromise
.then(datas => {
console.log(datas);
})
.catch(err => console.log(err));
</script>
4.2 promise的应用
4.2.1 异步加载图片
<style>
#img {
width: 80%;
padding: 10%;
}
</style>
<img src="https://img.mukewang.com/5e6af63d00011da318720764.jpg" alt="" id="img" />
<script>
// 异步加载图片
const loadImgAsync = url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error(`Could not load image at ${url}`));
};
img.src = url;
});
};
const imgDOM = document.getElementById('img');
loadImgAsync('https://2img.mukewang.com/5f057a6a0001f4f918720764.jpg')
.then(img => {
console.log(img.src);
setTimeout(() => {
imgDOM.src = img.src;
}, 1000);
})
.catch(err => {
console.log(err);
});
</script>
4.2.2 按需加载
只要前一张图片未成功加载,后面的图片就不会加载
img {
width: 500px;
height: 200px;
}
let arr = [
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp01%2F1ZZQ20QJS6-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639382820&t=005df0df320dfe0d7a43cfa605b65bca',
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffile02.16sucai.com%2Fd%2Ffile%2F2014%2F0427%2F071875652097059bbbffe106f9ce3a93.jpg&refer=http%3A%2F%2Ffile02.16sucai.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639382866&t=be9a0d43f75a116ba82d76cefc39da13',
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_source%2F53%2F0a%2Fda%2F530adad966630fce548cd408237ff200.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639382866&t=0ae45b7d5b66fdbe3d63310edd0af1aa'
];
function loadImg(url) {
return new Promise((resolve, reject) => {
//设置图片地址
let oImg = new Image();
oImg.src = url;
//图片加载成功
oImg.onload = () => {
resolve(oImg);
};
//图片加载失败
oImg.onerror = () => {
reject("图片加载失败");
};
});
}
let p = loadImg(arr[0]).then((oImg) => {
// console.log(oImg);
//将加载成功的图片加载到页面
document.body.appendChild(oImg);
return loadImg(arr[1]);
}).then((oImg) => {
// console.log(oImg);
//将加载成功的图片加载到页面
document.body.appendChild(oImg);
return loadImg(arr[2]);
}).then((oImg) => {
// console.log(oImg);
//将加载成功的图片加载到页面
document.body.appendChild(oImg);
}).catch((msg) => {
console.log(msg);
});
这时如果故意将第二张的图片地址弄错,则会:
4.3 promise 改造Ajax
<script>
function objTostr(data) {
data = data || {};
data.t = new Date().getTime();
var res = [];
for (var key in data) {
res.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
}
return res.join("&");
}
function ajax(option) {
return new Promise(function(resolve, reject) {
// 0.将对象转换为字符串
var str = objTostr(option.data);
// 1.创建一个异步对象
var xmlhttp, timer;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2.设置请求方式和请求地址
if (option.type.toLowerCase() === "get") {
xmlhttp.open(option.type, option.url + "?" + str, true);
// 3.发送请求
xmlhttp.send();
} else {
xmlhttp.open(option.type, option.url, true);
// 注意点: 以下代码必须放到open和send之间
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.send(str);
}
// 4.监听状态的变化
xmlhttp.onreadystatechange = function(ev2) {
if (xmlhttp.readyState === 4) {
clearInterval(timer);
// 判断是否请求成功
if (xmlhttp.status >= 200 && xmlhttp.status < 300 ||
xmlhttp.status === 304) {
// 5.处理返回的结果
// console.log("接收到服务器返回的数据");
// option.success(xmlhttp);
resolve(xmlhttp);
} else {
// console.log("没有接收到服务器返回的数据");
// option.error(xmlhttp);
reject(xmlhttp);
}
}
}
// 判断外界是否传入了超时时间
if (option.timeout) {
timer = setInterval(function() {
console.log("中断请求");
xmlhttp.abort();
clearInterval(timer);
}, option.timeout);
}
});
}
</script>
<script>
ajax({
type: "post",
url: "40.php",
// success: function (xhr) {
// let str = xhr.responseText;
// let json = JSON.parse(str);
// console.log(json);
// },
// error: function (xhr) {
// console.log(xhr.status);
// }
}).then(function(xhr) {
let str = xhr.responseText;
let json = JSON.parse(str);
console.log(json);
}).catch(function(xhr) {
console.log(xhr.status);
});
</script>
40.php,
<?php
$arr = array("name"=>"lwj", "age"=>"2");
$data = json_encode($arr);
echo $data;