1.什么是Promise
Promise 是异步解决方案。 Promise 的状态: pending(进行中),fulfilled(已成功),rejected(已失败),只有异步操作的结果才能够决定当前状态.
Promise 的状态改变只有两种可能:
- 从 pending 到 fulfill
- 从 pending 到 rejected
状态一旦改变,就不会再变,状态凝固,定型,会一直保持这个结果。这时就称为 resolved(已定型)。
如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的
2.基本用法
牢记
- new Promise()时,参数是一个函数,这个函数有两个参数(reslove,reject),这两个参数也都是函数。 ES6 中包含一个构造函数,生成Promise.
const p1 = new Promise((resolve, reject) => {
const value = 9
if(value > 1) {
resolve(`ok, value = ${value}`)
} else {
reject(`fail, value = ${value}`)
}
})
p1.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
通过修改 value 的值能够得到不同结果。
解析
Promise 接收一个函数作为参数,这个函数的两个参数是reslove, reject,这两个参数又是两个函数。
- resolve作用:将Promise对象的状态从 pending变为fulfilled,并且在异步操作成功时,将异步操作的结果作为参数传递出去
- reject作用:将Promise对象的状态从pending 变为rejected,在异步操作失败时调用,并将异步操作的错误,作为参数传递出去。
这两个概念很重要,第一,他们会直接改变状态,第二,他们会将异步操作结果返回出去,无论异步操作是否成功。
这里有一个例子有助于理解Promise(),来自与廖雪峰的Promise 教程。
const { log } = console
function add(input) {
return new Promise((resolve, reject) => {
log(`相加 ${input}`)
setTimeout(resolve, 500, input + input)
})
}
function multiply(input) {
return new Promise((resolve, reject) => {
log(`xxx ${input}`)
setInterval(resolve, 500, input * input)
})
}
const p2 = new Promise((resolve, reject) => {
log(`result start`)
resolve(11)
})
p2.then(multiply).then(add).then((res) => {
log(res)
})
Promise实例生成后,可以通过then()分别指定resolved 和rejected状态的回调函数。
- then()方法接受两个回调函数作为参数,第一个回调函数是成功时调用,第二个回调函数是失败时调用,第二个回调函数是可选的,
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
Promise新建后会立即执行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
这段代码书的输出顺序为1.Promise;2.Hi;3:resolved。 原因解析Promise新建后会立即执行,输出‘Promise’,然后,then方法中的回调函数,执行时机是在当前脚本的所有同步任务执行完之后才执行,所以resolved会在最后输出。
例子
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函数调用时,都带有参数。如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。
(个人理解: 这个回调函数或者可以说是then()方法的回调函数)
意为在resolve(this.response)的时候,this.response被作为参数传递给了then的回调函数,所以then的回调函数中的参数json就是this.response,
同理: reject(new Error(this.statusText)) 时,new Error(this.statusText)被作为参数传递给then的第二个回调函数,所以 then的第二个回调函数的参数error就是new Error(this.statusText)
这段代码中有对我有益的两点。
- 1.明白resolve(params),reject(params)调用时,如果他们带有参数,那么他们的参数会被传递给回调函数。
- 2.明白 this指向问题,反映了基本功不太扎实,函数作为对象属性上的方法被调用时,这个this指的就是这个对象,看到时还楞了一下,应注意。
这段代码如果用以下的两种方式去写,会更容易理解这个问题。其实就只是改变一下代码顺序问题, 就更容易理解了.
const getJSON = (url) => {
const promiseAjax = new Promise((reslove, reject) => {
const client = new XMLHttpRequest()
const handler = () => {
if(this.readyState !== 4) return
if(this.status === 200) {
reslove(this.response)
} else {
reject(this.statusText)
}
}
client.open('GET', url)
client.onreadystatechange = handler
client.responseType = 'json'
client.setRequestHeader('Accept', 'application/json')
client.send()
})
return promiseAjax
}
resolve()函数的参数除了正常值外,还可能是一个Promise实例
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))
代码结果:3s后打印错误信息。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
3.Promise.prototype.then()
它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
4.Promise.prototype.catch()
用于指定发生错误时的回调函数。
如果 Promise 状态已经变成resolved,再抛出错误是无效的。Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。
也就是说,错误总是会被下一个catch语句捕获。
Promise.resolve()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
上面的代码因为没有报错,跳过了catch方法,直接执行后面的then方法。此时,要是then方法里面报错,就与前面的catch无关了。
5.Promise.prototype.finally()
finally方法用于指定不管Promise最好的状态如何,都回去执行的操作。
finall方法的回调函数不接受任何参数,这意味着没办法知道前面的PromiSe状态是什么样的,表明finally中的代码,应该是与状态无关,不依赖Promised的执行结果。
5.Promise.prototype.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1,p2,p3,p4])
all方法接受一个数组作为参数,p1,p2,p3,p4都是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的回调函数。
总结:只要有一个被rejected,状态就为被rejected,只有全为fulfilled,状态才会为fulfilled
注意,如果作为参数的 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))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
代码解析:因为p2有自己的catch,而且状态也为rejected,所以会执行p2.catch()方法,而p2执行完catch后,状态变成了resolved,所以导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
6.Promise.race()
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
7.Promise.allSettled()
只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。
该方法返回Promise实例,一旦结束,状态只会变成fullfilled。
8.Promise.resolve()
有时候我们需要把现有对象转化为Promise对象,Promise.resovle()方法就是这个作用.
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
const p = Promise.resolve('foo')
p.then(res => {
console.log(res)
})
// 打印 foo
Promise.resolve() 的参数分为四种情况
- 参数是一个Promise实例。如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
- 参数是一个thenable对象。thenable对象指的是具有then方法的对象,比如:
const p = {
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。
9.Promise.reject()
同理:Promise.resolve()