什么是Promise?
我们使用console.dir(Promise)来看一下
Promise本身是一个构造函数,静态方法有all、allsettled、any、race、reject、resolve,当我们通过const promise = new Promise()创建一个Promise对象时,promise可以调用then、catch、finally方法。
ES6中提出,Promise是一种异步编程的解决方案,比起传统的解决方案——回调函数和事件,它更加的强大以及合理。
简单来说,Promise就是一个容器,里面保存着某个未来才会结束的结果。
Promise的简单用法
Promise是一个规范,它尝试用更加有好的方式来书写代码。Promise对象接收一个函数作为参数,函数提供两个参数:
- resolve:将
promise从未完成切换到成功状态,也就是上面提到的从pending切换到fufilled,resolve可以传递参数,下一级promise中的成功函数会接收到它; - reject:将
promise从未完成切换到失败状态,即从pending切换到rejected。
function runSync() {
const promise = new Promise(function (resolve, reject) {
const num = Math.ceil(Math.random() * 10);
if (num % 2 == 0) {
resolve("结果为偶数");
} else {
reject("结果为奇数");
}
})
return promise;
}
runSync().then(
function (data) {
console.log(data);
},
function (err) {
console.log(err);
}
)
这里需要注意的是,一旦Promise的状态发生改变,就表示Promise已完成决议(状态不会再更改)。且Promise只能被决议一次,如果你多次决议,她只会执行第一次决议。
const promise = new Promise(function (resolve, reject) {
resolve(1);
setTimeout(function () {
resolve(2);
}, 1000);
resolve(3);
})
promise.then(function (val) {
console.log(val); // 1
})
为什么会出现Promise?
Promise提供了对异步编程新的解决方案,传统的回调函数存在很大的问题,受制于js的单线程等原因导致不得不重复书写。当然,Promise并没有摆脱回调,只是改变了回调的位置。
异步编程——回调函数
使用传统的回调函数来解决异步问题时,可能会产生大量的嵌套导致代码难以阅读和修改(不优雅),也就是我们说的回调地狱。
$.ajax("url1", function response(res1) {
$.ajax("url2", function response(res2) {
$.ajax("url3", function response(res3) {
//...
})
})
})
异步编程——Promsie
Promise的实例具有then方法,它会返回一个新的Promise实例,因此我们可以像下面这样采用链式调用的方式来解决上面的问题;
function task1() {
$.ajax("url1", function response(res1) {
//...
})
}
function task2() {
$.ajax("url2", function response(res2) {
//...
})
}
function task3() {
$.ajax("url3", function response(res3) {
//...
})
}
var promise = new Promise((resolve, reject) => { resolve() });
promise.then(task1)
.then(task2)
.then(task3);
Promise的静态方法(all、race、allSettled)
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。Promise.all()方法接受一个数组作为参数,p1、p2都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2决定,分成两种情况:
- 只有
p1、p2的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2的返回值组成一个数组,传递给p的回调函数。
const p1 = new Promise(function (resolve, reject) {
resolve(111)
})
const p2 = new Promise(function (resolve, reject) {
resolve(222)
})
const p = Promise.all([p1, p2])
.then(function (data) {
console.log(data) // [111, 222]
})
- 只要
p1、p2之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,且不再继续执行,会传递给p的回调函数。
const p1 = new Promise(function (resolve, reject) {
reject(111)
})
const p2 = new Promise(function (resolve, reject) {
resolve(222)
})
const p = Promise.all([p1, p2])
.then(function (data) {
console.log(data) // Uncaught (in promise) 111
})
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
race存在竞速的意思,只要p1、p2之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(111)
}, 2000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(222)
}, 1000)
})
const p = Promise.race([p1, p2])
.then(function (data) {
console.log(data) // 222
})
Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束,这就是跟Promise.all()的区别。
const p1 = new Promise(function (resolve, reject) {
reject(111)
})
const p2 = new Promise(function (resolve, reject) {
resolve(222)
})
const p = Promise.allSettled([p1, p2])
.then(function (data) {
console.log(data)
})
// [
// { reason: 111, status: "rejected" },
// { status: "fulfilled", value: 222 }
// ]
在某些业务场景下我们不关心异步操作的结果,只关心这些操作有没有结束时,使用
Promise.allSettled()就很方便。
面试题
使用Pormise封装一下原生Ajax
function ajaxExample(method, url, data, async, timeout) {
const xhr = new XMLHttpRequest();
return new Promise(function(resolve, reject) {
xhr.open(method, url, async);
xhr.timeout = timeout;
xhr.onloadend = function() {
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
resolve(xhr);
} else {
reject({
errorType: "status_error",
xhr: xhr,
})
}
};
xhr.send(data);
// 错误处理
xhr.onabort = function() {
reject(new Error({
errorType: "abort_error",
xhr: xhr,
}));
};
xhr.ontimeout = function() {
reject({
errorType: "timeout_error",
xhr: xhr,
});
};
xhr.onerror = function() {
reject({
errorType: "onerror",
xhr: xhr,
});
};
})
}
封装一个Promise.all()方法
function ajaxExample(array) {
let num = 0;
const result = [];
return new Promise(
function (resolve, reject) {
array.forEach(promise => {
promise.then(
function (val) {
result.push(val);
num++;
if (num >= array.length) {
resolve(result);
}
},
function (err) {
reject(err);
}
)
})
}
)
}