什么是Promise
Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
创建Promise
Promise 对象是由关键字 new 及其构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。
const myFirstPromise = new Promise((resolve, reject) => {
// ?做一些异步操作,最终会调用下面两者之一:
//
// resolve(someValue); // fulfilled
// ?或
// reject("failure reason"); // rejected
});
想要某个函数拥有promise功能,只需让其返回一个promise即可。
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
};
为什么需要 promise ?
Javascript 是⼀⻔单线程语⾔,所以早期我们解决异步的场景时,⼤部分情况都是通过回调函数来进
⾏。
例如在浏览器中发送 ajax 请求,就是常⻅的⼀个异步场景,发送请求后,⼀段时间服务端响应之后我们
才能拿到结果。如果我们希望在异步结束之后执⾏某个操作,就只能通过回调函数这样的⽅式进⾏操
作
var dynamicFunc = function(cb) {
setTimeout(function() {
cb();
}, 1000);
}
dynamicFunc(function() {console.log(123)});
例如上⾯这个例⼦,这⾥的 dynamicFunc 就是⼀个异步的函数,⾥⾯执⾏的 setTimeout 会在 1s 之后调
⽤传⼊的 cb 函数。按照上⾯的调⽤⽅式,最终 1s 之后,会打印 123 这个结果。
同样的,如果后续还有内容需要在异步函数结束时输出的话,就需要多个异步函数进⾏嵌套,⾮常不利
于后续的维护。
setTimeout(function() {
console.log(123);
setTimeout(function() {
console.log(321);
// ...
}, 2000);
}, 1000);
Promise 基础
function promise1() {
return new Promise(function(resolve, reject) {
// 定义异步的内容
setTimeout(function() {
console.log('1s 后输出');
// 输出完成后,调⽤函数传⼊的 resolve 函数,将该 promise 实例标记为已完成,当前 promise 串
⾏继续执⾏
resolve();
}, 1000);
});
}
function promise2() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log('2s 后输出');
resolve();
}, 2000);
});
}
上⾯的两个 promise 实例,串联起来即可写为:
promise1().then(function() { return promise2(); }); 也可以简写为 promise1().then(promise2);
浏览器中执⾏之后,即可看到,1s 之后出现 1s 后输出 字样,再经过 2s 出现 2s 后输出 字样。在这个例⼦中我们能看到。当前 promise 如果状态变为已完成(执⾏了 resolve ⽅法),那么就会去执⾏ then ⽅法中的下⼀个 promise 函数。
同样的,如果我们的 promise 变为已拒绝状态(执⾏了 reject ⽅法),那么就会进⼊后续的异常处理函数中。
function promise3() {
return new Promise(function(resolve, reject) {
var random = Math.random() * 10; // 随机⼀个 1 - 10 的数字
setTimeout(function() {
if (random >= 5) {
resolve(random);
} else {
reject(random);
}
}, 1000);
});
}
var onResolve = function(val) {
console.log('已完成:输出的数字是', val);
};
var onReject = function(val) {
console.log('已拒绝:输出的数字是', val);
}
// promise 的 then 也可以接受两个函数,第⼀个参数为 resolve 后执⾏,第⼆个函数为 reject 后执⾏
promise3().then(onResolve, onReject);
// 也可以通过 .catch ⽅法拦截状态变为已拒绝时的 promise
promise3().catch(onReject).then(onResolve);
// 也可以通过 try catch 进⾏拦截状态变为已拒绝的 promise
try {
promise3().then(onResolve);
} catch (e) {
onReject(e);
}
这个例⼦使⽤了三种⽅式拦截最终变为「已拒绝」状态的 promise,分别是使⽤ then 的第⼆个参数,使⽤ .catch ⽅法捕获前⽅ promise 抛出的异常,使⽤ try catch 拦截代码块中 promise 抛出的异常同时我们还可以发现,在改变 promise 状态时调⽤ resolve 和 reject 函数的时候,也可以给下⼀步 then中执⾏的函数传递参数。这个例⼦中我们把随机⽣成的数字传给了 resolve 和 reject 函数,我们也就能在then 中执⾏函数的时候拿到这个值。
如何封装异步操作为 promise
发送 ajax 请求也可以进⾏封装:
function ajax(url, success, fail) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
success(this.response);
} else {
fail(new Error(this.statusText));
}
};
client.send();
};
ajax('/ajax.json', function() {console.log('成功')}, function() {console.log('失败')});
我们可以看到,调⽤ ajax ⽅法时需要传⼊success 和 fail 的回调函数进⾏调⽤。我们可以不传⼊回调函数,通过封装 promise 的⽅式,在原来的执⾏回调函数的地⽅更改当前 promise 的状态,就可以通过链式调⽤。
function ajaxAsync(url) {
return new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
client.send();
});
};
ajax('/ajax.json')
.catch(function() {
console.log('失败');
})
.then(function() {
console.log('成功');
})
如果你看完上面内容,恭喜你基础部分已经完成。已经可以进行项目实战了。
promise 规范解读
- promise 的状态
⼀个 Promise 的当前状态必须为以下三种状态中的⼀种:等待态(Pending)、已完成(Fulfilled)和已拒绝(Rejected)。 - 必须有⼀个 then ⽅法
⼀个 promise 必须提供⼀个 then ⽅法以访问其当前值和原因。
promise 的 then ⽅法接受两个参数: promise.then(onFulfilled, onRejected) 他们都是可选参数,同时他
们都是函数,如果 onFulfilled 或 onRejected 不是函数,则需要忽略他们。
Promise 构造函数上的静态⽅法
- Promise.resolve
返回⼀个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传⼊ promise 实例的值
var promise = Promise.resolve(123);
promise
.then(function(val) {
console.log('已完成', val);
});
// 已完成 123
同样的, Promise.resolve 的参数也可以处理对象,函数等内容,处理⽅式和上⾯规范中介绍的相同。
- Promise.reject
返回⼀个 promise 实例,并将它的状态设置为已拒绝,同时也将他的结果作为原因传⼊ onRejected 函数
var promise = Promise.reject(123);
promise
.then(null, function(val) {
console.log('已拒绝', val);
});
// 已拒绝 123
- Promise.all
返回⼀个 promise 实例,接受⼀个数组,⾥⾯含有多个 promise 实例,当所有 promise 实例都成为已完
成状态时,进⼊已完成状态,否则进⼊已拒绝状态。
var promise1 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(1);
resolve();
}, 1000)
});
}
var promise2 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(2);
resolve();
}, 2000);
});
}
Promise.all([promise1(), promise2()])
.then(function() {
console.log('全部 promise 均已完成');
});
注意,此时多个 promise 是同时进⾏的,也就是在上⾯这个例⼦中,等待 1s 打印 1 之后,再等待 1s 就
会打印 2 和「全部 promise 均已完成」
内部原理
/**
* promise的内部 原理
* @param {promise的数组} params
* @returns 返回结果成功 或失败
*/
const promiseall=(params)=>{
return new Promise((resolve,reject)=>{
let result=[];
for (let index = 0; index < params.length; index++) {
Promise.resolve(params[index]).then((res)=>{
result.push(res)
if (index===params.length-1) {
resolve(result)
}
},(error)=>{ reject(error)})
}
})
}
promiseall([promise1,promise2]).then(res=>{console.log("数据是",res)},error=>{console.log(error);})
- Promise.race
返回⼀个 promise 实例,接受⼀个数组,⾥⾯含有多个 promise 实例,当有⼀个 promise 实例状态改变时,就进⼊该状态且不可改变。这⾥所有的 promise 实例为竞争关系,只选择第⼀个进⼊改变状态的promise 的值
内部原理
var promise1 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(1);
resolve(1);
}, 1000)
});
}
var promise2 = function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(2);
resolve(2);
}, 2000);
});
}
Promise.race([promise1(), promise2()])
.then(function(val) {
console.log('有⼀个 promise 状态已经改变', val);
});
generator / async await 简介
1.async函数
函数的返回值为promise对象,promise对象的结果由async函数执行的返回值值决定
2.await函数
await右侧的表达式一般为promise对象,如果右侧是promise对象,则返回promise成功的值,如果为普通值就直接返回。
3.await必须写在async函数中,但async函数中可以没有await,如果await的promise失败了就会抛出异常,可以用try…catch来捕获。
function promise1() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(1);
resolve();
}, 1000)
});
}
function promise2() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(2);
resolve();
}, 2000);
});
}
// 使⽤ generator 函数
function* gen() {
yield promise1();
yield promise2();
}
var g = gen();
g.next();
g.next();
// 使⽤ async/await 函数` (async function() {
try {
await promise1();
await promise2();
console.log('已完成');
} catch (e) {
console.log(e);
console.log('已拒绝');
}
}());