带你快速入门Promise对象
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你快速入门Promise对象相关的知识,希望对你有一定的参考价值。
文章目录
带你快速入门Promise对象
什么是Promise?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象的两个特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从
pending
变为fulfilled
和从pending
变为rejected
。
Promise的基本用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例:
const promise = 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
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(function(value)
// success
, function(error)
// failure
);
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象
的状态变为resolved
时调用,第二个回调函数是Promise对象
的状态变为rejected
时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象
传出的值作为参数。而且Promise
新建后就会立即执行。
下面我们来看两个例子
- 通过
resolve
函数将Promise对象的状态从“未完成”变为“成功”
let p = new Promise(function(resolve,reject)
console.log("立刻执行")
resolve('Promise对象的状态变为resolved时调用');
);
console.log("==========")
p.then(function(resolved)
console.log(resolved);
);
上面代码中,Promise 新建后立即执行,所以首先输出的是立刻执行
。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
- 通过
reject
函数将Promise对象的状态从“未完成”变为“失败”
let p = new Promise(function(resolve,reject)
console.log("立刻执行")
reject('Promise对象的状态变为rejected时调用');
);
p.then(function(resolved)
console.log("成功");
,function(rejected)
console.log(rejected);
);
- 在异步操作失败时,会调用
reject
函数并将异步操作报出的错误,作为参数传递出去
let p = new Promise(function(resolve,reject)
console.log("立刻执行")
throw new Error('异步操作失败');
);
p.then(function(resolved)
console.log("成功");
,function(rejected)
console.log(rejected);
);
从上面可以看出如果在Promise
中使用 throw
语句的话,最终promise对象也变为Rejected
状态。
但实际开发中我们更加推荐,使用reject
的方法,上面的代码改写成下面这种方式
let p = new Promise(function(resolve,reject)
console.log("立刻执行")
reject(new Error('异步操作失败'));
);
p.then(function(resolved)
console.log("成功");
,function(rejected)
console.log(rejected);
);
关于为什么推荐使用reject而不是throw的话,可以参考下这篇文章:使用reject而不是throw
resolve
函数的参数除了正常的值以外,还可能是另一个 Promise
实例,比如像下面这样
const p1 = new Promise(function (resolve, reject)
console.log("p1 立刻执行")
setTimeout(() =>
console.log("p1 setTimeout 执行")
reject(new Error('fail'))
, 3000)
)
const p2 = new Promise(function (resolve, reject)
console.log("p2 立刻执行")
setTimeout(() =>
console.log("p2 setTimeout 执行")
resolve(p1)
, 1000)
)
p1.then(result => console.log("p1 异步成功",result))
.catch(error => console.log("p1 异步失败"))
p2.then(result => console.log("p2 异步成功",result))
.catch(error => console.log("p2 异步失败",error))
const p1 = new Promise(function (resolve, reject)
console.log("p1 立刻执行")
setTimeout(() =>
console.log("p1 setTimeout 执行")
resolve("p1 执行 resolve")
, 3000)
)
const p2 = new Promise(function (resolve, reject)
console.log("p2 立刻执行")
setTimeout(() =>
console.log("p2 setTimeout 执行")
resolve(p1)
, 1000)
)
p1.then(result => console.log("p1 异步成功",result))
.catch(error => console.log("p1 异步失败"))
p2
.then(result => console.log("p2 异步成功",result))
.catch(error => console.log("p2 异步失败",error))
上面的代码就是把p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending
,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved
或者rejected
,那么p2的回调函数将会立刻执行。
还有一点需要注意到的是,调用resolve
或reject
并不会终结 Promise
的参数函数的执行
上面代码中,调用resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为立即 resolved
的 Promise
是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
Promise.prototype.then()
Promise
实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。它的作用是为 Promise
实例添加状态改变时的回调函数。
它的作用是为 Promise
实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。
then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise实例)。关于每次调用then方法都会返回一个新创建的promise对象,可以参考这篇文章:专栏: 每次调用then都会返回一个新创建的promise对象
因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
const p1 = new Promise(function(resolve,reject)
resolve('success')
);
const p2 = p1.then(function(resolved)
console.log(resolved)
);
console.log(p1);
console.log(p2);
console.log(p1 === p2)
const p1 = new Promise(function(resolve,reject)
resolve('success')
);
const p2 = p1.then(function(resolved)
return "test" + resolved
);
p2.then(function(resolved)
console.log(resolved)
)
还有下面这个例子, 跟上面resolve
函数的参数除了正常的值以外,还可能是另一个 Promise
实例是一样的情况。
const p1 = new Promise(function(resolve,reject)
resolve('success')
);
const p2 = p1.then(function(resolved)
return new Promise(function(resolve,reject)
setTimeout(() =>
console.log("p1 setTimeout 执行")
resolve("p1 执行 resolve")
, 3000)
).then(function(res)
return 'test'
);
);
const p3 = p2.then(function(resolved)
console.log('2',resolved)
);
p1.then
方法指定的回调函数,返回的是另一个Promise
对象。这时,p2.then
方法指定的回调函数,就会等待这个新的Promise对象状态发生变化,才会调用p2.then
的回调
Promise.prototype.catch()
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
在异步操作中抛出错误,状态就会变为rejected
,就会调用catch()
方法指定的回调函数,处理这个错误。另外,then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获
let p = new Promise(function(resolve,reject)
throw new Error('失败')
);
p.catch(error=>
console.log(error)
)
let p = new Promise(function(resolve,reject)
try
throw new Error('失败');
catch(error)
reject(error);
);
p.catch(error=>
console.log(error)
)
下面这两种写法是等价的。
// 写法一
const promise = new Promise(function(resolve, reject)
try
throw new Error('test');
catch(e)
reject(e);
);
promise.catch(function(error)
console.log(error);
);
// 写法二
const promise = new Promise(function(resolve, reject)
reject(new Error('test'));
);
promise.catch(function(error)
console.log(error);
);
比较上面两种写法,可以发现reject()
方法的作用,等同于抛出错误…
如果在then()
方法指定的回调函数运行中抛出错误,也会被catch()
方法捕获
let p = new Promise(function(resolve,reject)
resolve('sucess')
);
p.then((resolved)=>
console.log(resolved)
throw new Error('失败');
).catch(error=>
console.log('error:',error)
)
但如果你catch
和then
的顺序调换的话,就没法被catch()
方法捕获.
let p = new Promise(function(resolve,reject)
resolve('sucess')
);
p.catch(error=>
console.log('error:',error)
).then((resolved)=>
console.log(resolved)
throw new Error('失败');
)
因为Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
因为有“冒泡”性质,catch
中能捕获到前面then
方法或catch
方法中抛出的错误,因此在实际开发中,我们更推荐使用catch(error)
,而不使用then
中的第二个参数去捕获。.then(null, err)
// bad
new Promise(function(resolve,reject)
resolve('sucess')
).then(function(data)
throw new Error('失败');
, function(err)
console.log("error:",err)
);
// good
new Promise(function(resolve,reject)
resolve('sucess')
).then(function(data)
throw new Error('失败2');
)
.catch(function(err)
console.log("error:",err)
);
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then
方法执行中的错误,也更接近同步的写法(try/catch)
。因此,建议总是使用catch()
方法,而不使用then()
方法的第二个参数。
关于使用then还是使用catch?可以参考这篇文章: then or catch?
跟传统的try/catch
代码块不同的是,如果没有使用catch()
方法指定错误处理的回调函数,Promise
对象抛出的错误不会传递到外层代码,即不会有任何反应。
const someAsyncThing = function()
return new Promise(function(resolve, reject)
// 下面一行会报错,因为x没有声明
resolve(x + 2);
);
;
someAsyncThing().then(function()
console.log('everything is great');
);
setTimeout(() => console.log(123) , 2000);
上面代码中,someAsyncThing()
函数产生的 Promise
对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined
,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到Promise
外部的代码,通俗的说法就是“Promise
以上是关于带你快速入门Promise对象的主要内容,如果未能解决你的问题,请参考以下文章