JavaScript 是一种单线程语言,这意味着它一次仅能执行一个任务。为了处理异步操作,JavaScript 提供了回调函数,但是随着项目处理并发任务的增加,回调地狱 (Callback Hell) 使异步代码很难维护。为此,ES6带来了Promise给了一种更清晰的异步操作模型。
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve和 reject。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
使用resolve和reject方法
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法调用
testPromise(true).then(function(msg){
console.log(msg);
},function(error){
console.log(error);
});
上面的代码的含义是给 testPromise方法传递一个参数,返回一个promise对象,如果为 true的话,那么调用promise对象中的 resolve()方法,并且把其中的参数传递给后面的 then第一个函数内,因此打印出 “hello world”, 如果为 false的话,会调用promise对象中的 reject()方法,则会进入 then的第二个函数内,会打印 No thanks;
第一个回调函数是Promise对象的状态变为 resolved时调用,第二个回调函数是Promise对象的状态变为 rejected时调用。其中第二个参数可以省略。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
当要写有顺序的异步事件时,需要串行写:
let promise = new Promise((resolve,reject)=>{
ajax('first').success(function(res){
resolve(res);
})
})
promise.then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
})
事件没有顺序或者关系时,还如何写呢?可以使用 all 方法来解决。
该方法相当于 then方法的第二个参数,指向 reject的回调函数。
p.then((data) => {
console.log('resolved',data);
},(err) => {
console.log('rejected',err);
}
);
// 或:
p.then((data) => {
console.log('resolved',data);
}).catch((err) => {
console.log('rejected',err);
});
all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个 promise对象。当数组中所有的 promise的状态都达到 resolved的时候,all方法的状态就会变成 resolved,如果有一个状态变成了 rejected,那么 all方法的状态就会变成 rejected。
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果为:[1,2,3]
})
race方法和 all一样,接受的参数是一个每项都是 promise的数组,但是与 all不同的是,当最先执行完的事件执行完之后,就直接返回该 promise对象的值。如果第一个 promise对象状态变成 resolved,那自身的状态变成了 resolved;反之第一个 promise变成 rejected,那自身状态就会变成 rejected。
let promise1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(1);
},2000)
});
let promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
},1000)
});
let promise3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
console.log(res);
//结果:2
},rej=>{
console.log(rej)};
)
race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
async function testAsy(){
return 'hello world';
}
let result = testAsy();
console.log(result)
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样:
async function testAsy(){
return 'hello world'
}
let result = testAsy()
console.log(result)
result.then(v=>{
console.log(v) // hello world
})
那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout 来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
如果用 async/await 来实现呢,会是这样:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
使用 try…catch…
async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
class MyPromise{
constructor(executor){
this.state = 'pending';
this.value = undefined; // 失败的值
this.error = undefined; // 成功的值
this.onFulfilledCallbacks = []; // 存储成功回调
this.onRejectedCallbacks = []; // 存储失败回调
const resolve = (value)=>{
if(this.state==='pending'){
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn=>fn(value)); // 执行所有成功回调
}
};
const reject = (error)=>{
if(this.state==='pending'){
this.state = 'rejected';
this.error = error;
this.onRejectedCallbacks.forEach(fn=>fn(error));
}
};
try{
executor(resolve,reject);
}catch(error){
reject(error);
}
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
return new Promise((resolve,reject)=>{
if(this.state==='fulfilled'){
try {
const result = onFulfilled?onFulfilled(this.value):this.value;
resolve(result);
} catch (error) {
reject(error);
}
}else if(this.state==='rejected'){
try {
const result = onRejected?onRejected(this.error):this.error;
reject(result);
} catch (error) {
reject(error);
}
}else{
this.onFulfilledCallbacks.push((value)=>{
try {
const result = onFulfilled ? onFulfilled(value) : value;
resolve(result);
} catch (error) {
reject(error);
}
})
this.onRejectedCallbacks.push((error) => {
try {
const result = onRejected ? onRejected(error) : error;
reject(result);
} catch (error) {
reject(error);
}
});
}
})
}
注意:
一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if(!Array.isArray(promises)){
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedResult)
}
},error=>{
return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})
该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可。
Promise.race = function (args) {
return new Promise((resolve, reject)=>{
for(let i = 0; i < args.length; i++){
Promise.resolve(args[i]).then(resolve,reject)
}
})
}