Promise对象之基本用法(1)
异步编程的几种实现方法:
javascript的执行环境是“单线程”,所谓的单线程指的是,一次只能执行一个任务。
就目前来看,异步编程有以下几种实现方式:
1.回调函数,一般多用setTimeout()函数:
//同步编程
function fn1() {
console.log("我是fn1");
}
function fn2() {
console.log("我是fn2")
}
fn1();
fn2();
//我是fn1
//我是fn2
//异步编程
function fn(callback) {
console.log("我是fn");
setTimeout(function () {
callback();
}, 0)
}
function fn3() {
console.log("我是fn3")
}
function fn4() {
console.log("我是fn4")
}
fn(fn3);
fn4();
// 我是fn
// 我是fn4
// 我是fn3
同步编程中,如果有多个任务,任务就会排队,前面一个任务完成后再执行后面一个任务,依此类推,程序的执行顺序与任务的排列顺序是一致的、同步的。fn1、fn2依次调用,因此会先执行fn1,fn1执行完后才会去执行fn2。
异步编程中,程序的执行顺序与任务的排列顺序是不一致的。以上代码中,先调用fn之后调用fn4,fn3是fn的参数。现在fn4已经在事件队列中了。在fn函数中存在setTimeout()函数,setTimeout()函数的作用是一定的时间(这里是0毫秒)之后将fn3放入事件队列(在将fn3放入事件队列之前fn4已经在事件队列中了)。因此执行顺序是:先执行fn中的console.log(),之后执行fn4中的console.log(),最后执行fn3中的console.log()。
2.事件监听。
3.发布/订阅。
4.Promise对象。
5. Generator函数(ES6)
6. async和await(ES7)
本节主要介绍Promise对象的相关内容,关于异步编程以及异步编程的其他方法以后还会写专门的文章来进行介绍。
(1)Promise的含义
Promise可以避免层层嵌套的回调函数,避免了“回调地狱”的出现。
Promise是一个统一的API,各种异步操作都可以用相同的方法进行处理:
在Promise对象中可以获得某个事件执行的结果(成功Fulfilled或者失败Rejected),之后可以通过javascript引擎提供的resolve函数和reject函数分别将事件执行的结果作为参数传递出去。之后就可以调用then、catch等方法对结果参数进行操作。
Promise对象有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。
Promise对象的状态改变只有两种可能:从Pending变为Fulfilled,从Pengding变为Rejected。状态只要发生改变之后就不会再变,而是一直保持这个结果,这时就成为Resolved(已定型)。
就算改变已经发生,再对Promise对象添加then、catch等方法,也会立即得到这个结果。
这与事件不同,事件的特点是,如果错过了它,再去监听它是得不到结果的。
(2)Promise的基本用法
1.Promise构造函数
Promise构造函数接受一个函数作为参数,这个函数又具有两个参数,分别是resolve函数和reject函数,这两个函数由javascript引擎提供,不用自己部署。
resolve函数的作用:在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
reject函数的作用:在异步操作失败时调用,并将异步操作的结果作为参数传递出去。
以下代码创建了Promise实例:
let promise = new Promise((resolve, reject) => {
//some code
});
let promise1 = new Promise(function(resolve, reject) {
//some code
});
2.then方法指定Resolved状态和Rejected状态的回调函数
Promise对象有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。为了方便,以下文章中的Resolved状态统一指Fulfilled状态,不包含Rejected状态。
Promise实例生成以后,可以使用then方法分别指定Resolved和Rejected状态的回调函数。
then方法可以接受两个回调函数作为参数:
第一个回调函数就是Promise对象的状态变为Resolved时调用;
第二个回调函数就是Promise对象的状态变为Rejected时调用;
其中第二个参数是可选的,不一定要提供。
let promise = new Promise(function (resolve, reject) {
//some code
});
promise.then(function (value) {
//Resolved状态
}, function (error) {
//Rejected状态
})
下面是一个Promise对象的简单例子:
timeout方法返回一个Promise实例,其中含有setTimeout()函数,在100毫秒后,异步操作执行成功,Promise实例的状态变为Resolved,调用resolve函数同时传递参数“done”,之后触发then方法绑定的第一个回调函数(“done”作为这个回调函数的参数)
function timeout(ms){
return new Promise((resolve,reject)=>{
setTimeout(resolve,ms,'done');
});
}
timeout(100).then((value)=>{
console.log(value);
})
//done
3.Promise新建后就会立即执行,then方法指定的回调函数在当前脚本所有同步任务执行完成后才会执行。
下面代码中,Promise新建后会立即执行,所以首先输出的是"Promise!"。then方法指定的回调函数在当前脚本的所有同步任务执行完成后才会执行,所以“Resolved!”最后输出。而console.log(“Hi!”)是当前脚本中的同步任务,所以"Hi!"第二个输出:
let promise = new Promise((resolve, reject) => {
console.log("Promise!");
resolve()
});
promise.then(function () {
console.log("Resolved!")
});
console.log("Hi!")
//Promise!
//Hi!
//Resolved!
4.resolve函数和reject函数在调用时可以带有参数,并将参数传递给回调函数
reject函数的参数通常是Error对象的实例,表示抛出的错误:
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);
});
5.resolve函数的参数可以是另外一个Promise实例:
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
p2的resolve方法将p1作为参数,这时p1的状态传递会给p2。也就是说p1的状态决定了p2的状态:
如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;
如果p1的状态已经是Resolved或者Rejected,那么p2的相应回调函数将会立刻执行。
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(function(value){
console.log(value)
},function(error){
console.log(error)
})
// Error: fail
上面代码中,p1是一个 Promise实例,3 秒之后状态变为Rejected。p2的状态在 1 秒之后变为Resolved,调用resolve函数,返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句根据p1的状态进行调用。又过了 2 秒,p1的状态变为Rejected,调用reject函数,进而触发then的第二个回调函数。
6.调用resolve函数或者reject函数并不会终结Promise中代码的执行:
let promise = new Promise((resolve, reject) => {
console.log("Promise1");
resolve(1);
console.log("Promise");
});
promise.then(function (value) {
console.log(value)
});
//Promise1
//Promise
//1
调用resolve函数的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务( console.log(“Promise1”)和console.log(“Promise”))。
一般来说,调用resolve函数或reject函数以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve函数调用或reject函数调用的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
本篇文章主要介绍了Promise对象的基本用法,下一篇ES6 Promise对象之实例方法(2),会接着这一篇文章继续介绍Promise对象的实例方法以及应用等方面的内容。