ES6里面的Promise对象的方法使用
Promise的含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。
Promise对象的特点:
1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:①pending(等待状态,这个是处于未决阶段,表示当前事情还处于挂起状态最终结果没有出现)、resolved(已处理,处于已决阶段,表现是事情已经出现了结果,这个结果是成功的结果,可以按照当前结果的正常逻辑顺序继续下去的结果)、rejected(已拒绝,处于已决阶段,表现是事情已经出现了结果,这个结果是未成功的结果,当前结果的并不能按照正常逻辑顺序继续下去的结果)
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
把事情从pendding状态推向resolved状态或者rejected状态的过程,会传递一些数据,这些数据代表真实数据或者无效数据
当事情已到达已决阶段以后,通常要进行后续处理。不同的已决解决,决定了不同的后续处理
resolved 是一个正常的已决状态,后续处理表示为 thenable
rejected 是一个非正常的已决状态,后续处理表示为 catchable
后续的处理可能有多个,因此会形成任务队列,这些后续处理会按照顺序,当达到对应的状态时依次执行
Promise的缺点
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段。
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。
基本用法
Promise对象是一个构造函数,用来生成Promise实例。
下面代码创建了一个Promise实例:
const promise = new Promise(function(resolve, reject) {
resolve(value);//执行这个,如果这一句没有就会执行下面的这一句
reject(error);
});
zhen方法的使用
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(value =>{
console.log(value);//这个是你处于resolve状态是执行的
}, error => {
console.log(error);//这个是你处于reject状态时执行的
});
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
//上面的代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
Promise 新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
//上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
下面是一个用Promise对象实现的 Ajax 操作的例子。
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
//上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
注意:
1、如果你的返回值为一个Promise对象,那么推出的是一种状态的结果。
2、调用resolve或reject并不会终结 Promise 的参数函数的执行。
处理异步消息的注意:
1、如果当前的promise是未决状态的,得到的新的promise是挂起状态的
2、多个promise对象串联的时候如何完成
3、如果前面的promise的后续处理,返回的是一个promise,则返回的promise状态和后续处理返回的promise状态保持一致
4、Promise并不是消除回调,只是让回调变得更简单,变更可控,解决promise的回调
catch()的使用
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(posts=> {
// ...
}).catch(error=> {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
pl.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
pl.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
下面是一个例子。
const promise = new Promise((resolve, reject)=>{
throw new Error('test');
});
promise.catch(error=>{
console.log(error);
});
// Error: test
上面代码中,promise抛出一个错误,就被catch()方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
// 写法一
const promise = new Promise((resolve, reject)=>{
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch((error)=>{
console.log(error);
});
// 写法二
const promise = new Promise((resolve, reject)=>{
reject(new Error('test'));
});
promise.catch((error)=> {
console.log(error);
});
比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。
如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise((resolve, reject)=>{
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
//上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
finally()的使用方法
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise.then(result => {···}).catch(error => {···}).finally(() => {···});
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
finally本质上是then方法的特例。
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
它的实现也很简单。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
//上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
all()的使用方法
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
下面是一个具体的例子。
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
//上面代码中,promises是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。
下面是另一个例子。
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
上面代码中,booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数。
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
race()的使用方法
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(console.log).catch(console.error);
上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。
resolve()的使用方法
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。
Promise.resolve()等价于下面的写法。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.resolve方法的参数分成四种情况。
1、参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2、参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。
3、参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。
4、不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
const p = Promise.resolve();
p.then(function () {
// ...
});
上面代码的变量p就是一个 Promise 对象。
需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(()=>{
console.log('three');
}, 0);
Promise.resolve().then(()=>{
console.log('two');
});
console.log('one');
// one
// two
// three
上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。
reject()的使用方法
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const pro = new Promise((resolve,reject) => {
reject(1)
})
const pro = Promise.reject(1)
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。
应用
加载图片
我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
Generator 函数与 Promise 的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
上面代码的 Generator 函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。
Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
(async () => f())().then(...)
需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。
(async () => f())().then(...).catch(...)
第二种写法是使用new Promise()。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。
database.users.get({id: userId}).then(...).catch(...)
但是database.users.get()可能还会抛出同步错误,这时你就不得不用try…catch去捕获。
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。
Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。
在不使用Promise上面的方法的情况下实现promise的方法和功能,完整代码如下:
const MyPromise = (() => {
// 定义三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// 不让外界访问,定义符号属性,状态,数据
const PromiseStatus = Symbol("PromiseStatus") //当前状态
const PromiseValue = Symbol("PromiseValue") //当前数据
const changeStatus = Symbol("changeStatus");
// 定义两个任务队列成功的后续处理和失败的后续处理
const thenables = Symbol("thenables");
const catchables = Symbol("catchables");
// 后续处理函数
const settleHandle = Symbol("settleHandle")
// 创建串联Promise
const linkPromise = Symbol("linkPromise");
// 定义构造函数
return class MyPromise {
// newStatus 新的状态
// newValue 新的数据
// queue 执行的作业队列
[changeStatus](newStatus, newValue, queue) {
if (this[PromiseStatus] !== PENDING) {
return
}
this[PromiseStatus] = newStatus;
this[PromiseValue] = newValue;
queue.forEach(handler => handler(newValue))
}
constructor(executor) {
this[PromiseStatus] = PENDING; //初始时状态为pending
this[PromiseValue] = undefined; //初始时数据为undefined
// 定义任务队列的数组
this[thenables] = []; //后续处理函数的数组 对应resolved状态
this[catchables] = []; //后续处理 对应rejected状态
// 定义成功,失败的推向函数,不能使用function函数,不然this的指向会发生变化
const resolve = data => {
this[changeStatus](RESOLVED, data, this[thenables]);
}
const reject = reason => {
this[changeStatus](REJECTED, reason, this[catchables])
}
// executor(resolve,reject);
// 可以使用new Error推向失败的阶段
try {
executor(resolve, reject);
} catch (err) {
reject(err)
}
}
// 定义then方法
then(thenable, catchable) {
return this[linkPromise](thenable, catchable)
}
//定义catch方法
catch (catchable) {
return this[linkPromise](undefined, catchable)
}
// 提取公共代码
// handler 后续处理函数
// immediateStatus 立即执行的状态
// queue 作业队列
[settleHandle](handler, immediateStatus, queue) {
if (typeof handler !== "function") {
return
}
if (this[PromiseStatus] === immediateStatus) {
setTimeout(() => {
handler(this[PromiseValue])
}, 0)
} else {
queue.push(handler)
}
}
// 定义串联函数
[linkPromise](thenable, catchable) {
function exec(data, handler, resolve, reject) {
try {
//得到promise的处理结果
const result = handler(data);
if (result instanceof MyPromise) {
result.then(d => {
resolve(d)
}, err => {
reject(err)
})
} else {
resolve(result)
}
} catch (err) {
reject(err);
}
}
return new MyPromise((resolve, reject) => {
this[settleHandle](data => {
exec(data, thenable, resolve, reject)
}, RESOLVED, this[thenables])
this[settleHandle](reason => {
exec(reason, catchable, resolve, reject)
}, REJECTED, this[catchables])
})
}
//创建一个在其原型上面的静态方法,如果你的方法中只有有一个处于失败状态就失败
static all(proms) {
return new Promise((resolve, reject) => {
const results = proms.map(p => {
const obj = {
result: undefined,
isResolved: false
}
p.then(data => {
obj.result = data;
obj.isResolved = true;
const unResolved = results.filter(r => !r.isResolved);
if (unResolved.length === 0) {
// 全部完成
resolve(results.map(r => r.result))
}
}, reson => {
reject(reson)
})
return obj;
})
})
}
//创建一个在其原型上面的静态竞赛的方法
static race(data) {
return new Promise((resolve, reject) => {
proms.forEach(p => {
p.then(data => {
resolve(data);
}, err => {
reject(err);
})
})
})
}
//创建一个在其原型上面的静态已处理状态的方法
static resolve(data) {
if (data instanceof MyPromise) {
return data
} else {
return new MyPromise(resolve => {
resolve(data);
})
}
}
//创建一个在其原型上面的静态已拒绝状态的方法
static reject(reason) {
return newMyPromise((resolve, reject) => {
reject(reason)
})
}
}
})()