Promise快速入门
什么是Promise
Promise对象代表一个异步操作,它用来声明一个尚未完成且预计在未来完成的异步操作
同步和异步
同步
同步模式其实也就是单线程模式,在最早期的网页设计中,只有同步模式,所以你可能会看到进入一个网页的时候浏览器一直处于加载状态好几分钟的情况。就是因为它是单线程的,一个资源或者一个操作没有做完就不会继续往下处理,导致线程阻塞,整个页面都卡主。
例如下面的例子,在同步模式下需要等待睡眠之后才能执行打印操作,因为一个耗时很长的操作导致后面的程序都要等待执行,会给用户带来很不好的体验,因此异步模式应运而生。
const a = 1;
sleep(10000000);
console.log(a);
异步
异步模式与同步模式相反,它允许同时执行多个任务,函数调用后不会立刻将结果返回。最常见的异步模式就是setTimeout这个的函数了。
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function testTimeout() {
setTimeout(function() {
console.log("in setTimeout");
}, 0);
console.log("out setTimeout");
}
</script>
</head>
<body>
<button onclick="testTimeout();">测试timeout</button>
</body>
</html>
执行结果如下:
可以看到,虽然testTimeout函数在前面,而且定时器的延时设置为0了,但是仍然先打印了后面的out setTimeout。这是由于setTimeout函数是异步执行的,异步任务会在当前所有的同步任务执行结束之后在执行。如果有面的同步任务中产生了错误或者有死循环存在,前面的"in setTimeout"可能永远不会打印出来。
Promise的基本使用方法
promise的状态
promise代表了一个当前未完成,在将来某个时间会完成的操作,它具有三种状态:pending(初始值),fulfilled(操作成功),rejected(操作失败)。
promise的状态可以从pending变为fulfilled或者从pending变为rejected,而且一旦promise的状态改变了,就不能在改变。
promise的基本结构
promise概述
promise通过new 关键字创建一个Promise对象,其中这个对象创建时会传入一个函数作为参数,这个函数的参数为resolve和reject,这两个函数就是回调函数(具体什么是回调函数本文不再做过多介绍,百度一下,你就知道哦)。
resolve函数作用:在定义的异步操作执行成功时将结果作为它的参数传递出去给外部使用;
reject函数作用:在定义的异步操作执行失败时将错误信息作为它的参数传递出去给外部使用;
创建promise
创建promise的格式如下
const promise = new Promise(function (resolve, reject) {
// do something...
if (执行成功) {
resolve(result);
} else {
reject(errMsg);
}
});
通过then函数执行后续操作
promise在定义之后通过**.then**来指定resolve和reject的回调函数,例子接上文中promise的定义
promise.then(function (data){
// do something...
}, function (errMsg) {
// do something...
});
.then方法会返回一个Promise对象,then方法包含两个参数,一个是Promise对象从pending变为fulfilled状态时执行的函数,一个是Promise对象从pending变为rejected状态时执行的函数,这两个函数都以从Promise对象传出的值作为参数进行一些操作。此处的例子中就是从定义中传出的data和errMsg数据作为参数。
完整例子如下如下:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function testTimeout() {
setTimeout(function() {
console.log("in setTimeout");
}, 0);
console.log("out setTimeout");
}
function testPromise() {
const promise = new Promise(function (resolve, reject) {
// setTimeout
setTimeout(function () {
// 执行成功, 将数据123传递出去
resolve("123")
}, 2);
});
promise.then(function (data) {
// 直接将传递出来的数据打印
console.log(data);
});
}
</script>
</head>
<body>
<button onclick="testTimeout();">测试timeout</button><br />
<button onclick="testPromise();">测试Promise</button>
</body>
</html>
点击按钮执行结果如下
整理我们通过setTimeout模拟一个异步操作,实际使用过程中也可以是其他操作,比如一个ajax请求,如果请求成功,就将接口返回结果通过resolve函数传递出来,如果接口请求失败就将错误信息通过reject函数返回回来。
此外,前面提到过then返回的也是一个Promise对象,因此,promise.then()之后可以继续通过then函数进行处理,这里放出了函数代码,其他代码可参照上文:
function testPromiseThenChain() {
const promise = new Promise(function (resolve, reject) {
// setTimeout
setTimeout(function () {
// 执行成功, 将数据123传递出去
resolve(1)
}, 2);
});
promise.then(function (data) {
// 直接将传递出来的数据打印
console.log(data);
return data * 2
}).then(function (data) {
console.log(data);
});
执行结果如下:
第一个then接收了promise中传出的异步执行结果1,第二个then接收了第一个then执行的结果data*2也就是2作为参数。
通过catch处理异常
上面说到可以通过.then制定两个函数,分别作为执行成功和失败时执行的函数,其实第二个参数不是必须的,可以只指定一个执行成功时的函数,然后通过catch处理。
function testCatch() {
const promise = new Promise(function (resolve, reject) {
throw new Error("test error");
});
promise.then(function (data) {
console.log(data);
}).catch(function (errMsg) {
console.log("error信息:" + errMsg);
});
}
执行结果如下:
通过then函数执行执行失败时的函数和通过catch捕捉异常有一个区别,如果在then中指定的执行成功时执行的函数中抛出了异常,then中指定的异常处理函数是无法捕捉到的,而catch可以捕捉到,因此个人觉得最好的写法是then中只定义成功时执行的函数,异常通过catch在末尾捕获。也就是下面这种结构
const promise = new Promise(function (resolve, reject) {
if (success) {
resolve(data);
} else {
reject(errMsg);
}
});
promise.then(function (data) {
// do something with data
}).then(function (data) {
// do something with data return from then
}).catch(function (errMsg) {
// do
});
promise的特点
- promise在创建时就执行,无法取消(这其实也是它的弊端之一)
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function test() {
const promise = new Promise(function (resolve, reject) {
console.log(1);
setTimeout(function () {
console.log(2);
resolve("success!");
}, 2);
console.log(3);
});
console.log(4);
promise.then(function (data) {
console.log(5);
}).catch(function (errMsg) {
console.log(6);
});
console.log(7);
}
test();
</script>
</head>
<body>
</body>
</html>
执行结果为:
2. 链式调用中直到catch住了异常才会继续向下执行
请看例子,注意代码中的注释
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function test2() {
const promise = new Promise(function (resolve, reject) {
// 将Promise的状态置为rejected
reject("报错了");
});
promise.then(function (data) {
// 异常未被捕获, 不执行
console.log(1);
}).then(function (data) {
// 异常未被捕获, 不执行
console.log(2);
}).catch(function (errMsg) {
// 捕获异常并打印信息
console.log(errMsg);
}).then(function (data) {
// 异常已被捕获, 继续执行
console.log(3);
});
}
test2();
</script>
</head>
<body>
</body>
</html>
执行结果如下:
3. 异步回调函数中如果出现了异常,不会被catch住
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function test3() {
const promise = new Promise(function (resolve, reject) {
setTimeout(function () {
throw new Error("抛出异常信息啦");
}, 2);
});
promise.then(function (data) {
console.log(data);
}).catch(function (errMsg) {
console.log(errMsg);
});
}
test3();
</script>
</head>
<body>
</body>
</html>
执行结果如下:
其他函数
除了上面介绍的最关键的.then和.catch之外,Promise还提供了其他几个函数方便使用
all
all方法接收多个Promise对象组成的一个数组,当数组中的全部异步操作住行完毕时返回一个数组对应每个Promise中resolve出来的值。多个promise是同时开始,同时执行的。
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
const promise1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1);
}, 1000);
});
const promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2);
}, 2000);
});
const promise3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3);
}, 3000);
});
Promise.all([promise1, promise2, promise3]).then(function (values) {
console.log(values);
}).catch(function (errMsg) {
console.log(errMsg);
});
</script>
</head>
<body></body>
</html>
大约3秒后,控制台结果如下:
从结果也可以看到promise1、promise2、promise3是同时开始执行的,否则执行时间应该为1+2+3=6秒,然后才会打印。
race
race函数也接收多个promise作为参数,但是与all不同的是,只要其中一个promise执行结束,race函数就会返回,而不再等待其他异步操作执行
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
const promise1 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("first");
resolve(1);
}, 1000);
});
const promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("second");
resolve(2);
}, 2000);
});
const promise3 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("third");
resolve(3);
}, 3000);
});
Promise.race([promise1, promise2, promise3]).then(function (values) {
console.log(values);
}).catch(function (errMsg) {
console.log(errMsg);
});
</script>
</head>
<body></body>
</html>
将上一个例子中的all函数改为race函数,再次执行如下:
从结果可以查看出,race函数返回了最开执行结束的Promise对象的执行结果,其他Promise结果虽然没返回,但是也都执行了。
resolve && reject
resolve和reject方法可以让Promise对象直接进入对应的fulfilled状态和rejected状态,是一种创建Promise对象的快捷方式
前面介绍过通过new的方式新建一个Promise对象,此时Promise对象的状态是pending,而resolve和reject方法提供了一种创建Promise的快捷形式
Promise.resolve("sucess");
// 等同于
new Promise(function (resolve) {
resolve("success");
});
Promise.reject("error message");
// 等同
new Promise(function (resolve, reject) {
reject("error message");
});
通过Promise.resolve和Promise.reject方法创建的Promise对象会直接进入对应的状态,并将对应的执行成功结果或报错信息提供给外界。
同样的,这两个函数返回的也是Promise对象,也就是可以进行Promise.resolve(“success”).then(…).then(…).catch()这类链式调用操作。