所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作
都可以用同样的方法进行处理。
1.Promise特点:
1.1
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
1.2
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
2.基本用法
Promise对象是一个构造函数,用来生成Promise实例
{
let promise = new Promise(function (resolve, reject) {
// if (/* 异步操作成功 */) {
// resolve(value);
// } else {
// reject(error);
// }
})
}
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
{
let promise = new Promise(function (resolve, reject) {
// resolve('women');
reject('cuowu');
}).then(function (value) {
console.log(value)
}, function (value) {
console.log(value)
});
promise.then(function (value) {
// success
console.log(value)
}, function (error) {
// failure
});
}
{
function timeout(ms) {
return new Promise((resolve, rejected) => {
setTimeout(resolve, ms, 'done');
})
}
timeout(1000).then((value) => {
console.log(value)
})
}
Promise 新建后就会立即执行。
{
let promise = new Promise(function (resolve, rejected) {
console.log('Promise');
resolve();
});
promise.then(function () {
console.log('resolved.');
});
console.log('Hi!');
}
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,
将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
异步加载图片
{
function loadImageanysc(url) {
return new Promise(function (resolve, rejected) {
let image = new Image();
image.onload = function () {
resolve(url);
}
image.onerror = function () {
rejected(url)
}
image.src = url;
// console.log(imag.src)
}).then(function (url) {
console.log(url + '图片加载出来了')
},
function (name) {
console.log(url + '图片加载不出来')
}
)
}
loadImageanysc('https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2310514390,3580363630&fm=27&gp=0.jpg')
}
{
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函数调用时,都带有参数。
{
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
}
上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
{
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
}
Error: fail
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。
由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。
又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
3.Promise.prototype.then()
P romise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作), 这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
{
getJSON("/post/1.json").then(function (post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err) {
console.log("rejected: ", err);
});
}
使用箭头函数更加简介
{
getJSON('/post/1.json').then(post => {
return getJSON(post.commentUrl);
}).then(comments => {
console.log("resolved: ", comments);
}, err => {
console.log("rejected: ", err)
})
}
4.Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
{
const promise = new Promise(function (resolve, reject) {
throw new Error('test');
});
promise.catch(function (error) {
console.log(error);
});
// Error: test
}
上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
写法一
{
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方法的作用,等同于抛出错误。
如果 Promise 状态已经变成resolved,再抛出错误是无效的。
{
const promise = new Promise(function (resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function (value) { console.log(value) })
.catch(function (error) { console.log(error) });
// ok
}
一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
{
// bad
promise
.then(function (data) {
// success
}, function (err) {
// error
});
// good
promise
.then(function (data) { //cb
// success
})
.catch(function (err) {
// error
});
}
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,
也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。
5.romise.prototype.finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
{
function getPromise() {
return new Promise((resolve, reject) => {
// debugger
resolve();
}).then(() => {
console.log('成功')
}).catch(() => {
console.log('失败或错误')
}).finally(() => {
console.log('最终结果')
})
}
getPromise();
}
服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
{
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
}
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,
finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
finally本质上是then方法的特例。
{
function getPromise() {
return new Promise((resolve, reject) => {
// debugger
resolve();
})
}
getPromise().finally(() => {
});
//等同于
getPromise().then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
})
}
从下面的实现还可以看到,finally方法总是会返回原来的值。
{
// resolve 的值是 undefined
Promise.resolve(2).then(() => { }, () => { })
// resolve 的值是 2
Promise.resolve(2).finally(() => { })
// reject 的值是 undefined
Promise.reject(3).then(() => { }, () => { })
// reject 的值是 3
Promise.reject(3).finally(() => { })
}
6.Promise.all()
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
{
let 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 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
{
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
}
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
7.Promise.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 实例,再进一步处理。
8.Promise.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方法的对象,比如下面这个对象。
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
(3)参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
(4)不带有任何参数
Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。
9.Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
10.应用
10.1加载图片
10.2 Generator 函数和Promise的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。