在MDN中,定义promise的只有一句话:promise对象用于表示一个异步操做的最终完成(或失败),及其结果值。javascript
从这句话的定义咱们能够抓住几个关键词:promise是对象、异步操做、最终状态及结果值。java
在真正了解promise是什么前,咱们不得不思考,promise的出现到底是为了解决什么问题。ajax
背景promise
javascript是单线程语言:单线程指若是有多个任务必须先排队,前面的任务执行完成后,后面的任务再执行。浏览器
若是在函数返回结果的时候,调用者可以拿到预期的结果(就是函数计算的结果),那么这个函数就是同步函数。bash
console.log('joseydong'); // 执行后,得到了返回结果
function wait(){
var time = (new Date()).getTime();//获取当前的unix时间戳
while((new Date()).getTime() - time < 5000){}
console.log('你好个人名字是');
}
wait();
console.log('josey');
console.log('说得太慢了');
复制代码
在这段代码中,wait函数是一个须要耗时5秒的函数,在这5秒中,下面的console.log()函数只能等待。异步
这就是同步的缺点:若是一个函数是同步的,即便调用函数执行任务比较耗时,也会一直等待直到获得执行结果。async
若是在函数返回的时候,调用者还不能获得预期的结果,而是未来经过必定的手段获得,这就叫异步。函数
当执行异步函数的时候,发出调用以后会立刻返回,但不会是返回预期结果;调用者没必要默默等待,当获得返回结果的时候回经过回调函数主动通知调用者。优化
异步操做是会在某个时间点触发一个函数的调用。
好比AJAX就是典型的异步操做:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
复制代码
把回调函数success(request.responseText)和fail(request.status)写在一个ajax操做里,不利于维护,也很差复用。
思考一种更好的写法,好比:
var ajax = ajaxGet('url');
ajax.ifSuccess(success)
.ifFail(fail)
复制代码
这种链式写法的好处在于:先统一执行ajax逻辑,不关心如何处理返回结果,而后根据返回结果是成功仍是失败,在未来的某个时候调用success函数或者fail函数。
这种承诺(promise)未来会执行的对象被称为promise对象。
Promise有各类开源实现,在ES6中被统一规范,由浏览器直接支持。
promise是一个对象,从这个对象中能够获取异步操做的信息。
它表明一个异步操做,有三种状态:
有了promise,就能够将异步操做以同步操做的流程表达,但它也有以下缺点:
建立promise
var promise = new Promise(
/* executor */
function(resolve,reject){
//...
}
);
复制代码
executor函数由Promise实例当即执行,传递resolved和reject函数。
在executor内部,promise有以下可能的变化:
一、若是在executor方法的执行过程当中抛出了任何异常,那么promise当即被拒绝,至关于reject方法被调用,executor的返回值也就被忽略。
二、若是一个promise对象处在resolved或者rejected状态,那么也能够被称为settled状态。
处理Promise实例
1.Promise对象的错误具备冒泡性质,会一直向后传递,直到被捕获为止。也就是说错误必定会被catch语句捕获。
将多个Promise实例,包装成一个新的Promise实例
Promise.all(iterable):当全部在可迭代参数中的promises已完成时,或者当传递过程当中的任何一个promise进入rejected状态,返回promise。 var promise = Promise.all([p1,p2,p3]);
p1&&p2&&p3 都返回resolved => promise返回resolved
p1||p2||p3中任意一个返回rejected=>promise状态就变成rejected,此时第一个被reject的实例返回值会传递给p的回调函数。
复制代码
防止then嵌套 由于then中return的仍是promise,因此会执行完里面的promise再执行外面的then。此时最好将其展开,也是同样的结果,并且会更好读。
// ------------ 很差的写法 -------------
new Promise (resolve => {
console.log('Step 1');
setTimeout(()=> {
resolve('100');
},1000)
}).then(value => { // value => 100
return new Promise(resolve => {
console.log('Step 1-1');
setTimeout(() => {
resolve('110');
},1000);
})
.then(value => { // value => 110
console.log('Step 1-2');
return value;
})
.then(value => { // value => 110
console.log('Step 1-3')
return value;
})
})
.then(value => {
console.log(value); // value = 110
console.log('Step 2')
})
复制代码
// ------------ 好的写法 ------------
new Promise((resolve) => {
console.log("Step 1");
setTimeout(() => {
resolve("100");
}, 1000);
})
.then((value) => {
// value => 100
return new Promise((resolve) => {
console.log("Step 1-1");
setTimeout(() => {
resolve("110");
}, 1000);
});
})
.then((value) => {
// value => 110
console.log("Step 1-2");
return value;
})
.then((value) => {
// value => 110
console.log("Step 1-3");
return value;
})
.then((value) => {
console.log(value); // value = 110
console.log("Step 2");
});
复制代码
使用.catch()捕捉错误
一般状况下promise有以下两种处理方式:
// ------------ 很差的写法 -------------
promise.then(function(data) {
// success
}, function(err) { //仅处理promise运行时发生的错误。没法处理回调中的错误
// error
});
// ------------ 好的写法 ------------
promise.then(res => {
// success
}).catch(err => { // 处理 promise 和 前一个回调函数运行时发生的错误
// error
});
复制代码
由于promise抛出的错误不会传递到外层,当使用第一种写法时,成功回调的错误没法处理,所以建议使用catch方法。
async:异步,await:异步等待。
简单来讲async用于声明一个function是异步的,而await用于等待一个异步方法执行完成。
语法规定await只能出如今async函数中,async函数返回的是一个Promise对象。
promise的特色是无需等待,因此在没有await的状况下执行async函数,它会当即执行,返回一个promise对象而且不会阻塞后面的语句,就和普通的promise同样。
await等待的是一个promise对象/其余值。
由于async函数返回的是一个promise对象,因此await能够用于等待一个async函数的返回值。而且,它能够等待任意表达式的结果,因此await后面能够接普通函数调用或者直接量。
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething(); // Promise对象
const v2 = await testAsync(); // 普通函数
console.log(v1, v2);
}
test();
复制代码
await是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西。
若是等到的不是个promise对象,那么await表达式的运算结果就等于它等到的东西。
若是等到的是个promise对象,那么await就会阻塞后面的代码,等着promise对象的resolved状态,而后获得resolve的值,做为await表达式的运算结果。
一、这就是await必须放在async函数内部的缘由:async函数调用不会形成阻塞,它内部全部的阻塞都被封装在一个promise对象中异步执行。
不使用async/await
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve('hahahha'),1000);
});
}
takeLongTime().then(value => console.log('heihei',value))
复制代码
使用async/await
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve('hahahha'),1000);
});
}
async function test() {
const value = await takeLongTime();
console.log(value);
}
test();
复制代码
async/await的优点在于处理then链
Promise经过then链来解决多层回调问题,但若是须要同时处理由多个Promise组成的then链,就能够用async/await来进一步优化。
假设一个场景,分多个步骤完成,每一个步骤都是异步的,并且依赖于上一个步骤的结果。
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();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1580.487ms
复制代码
用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();
复制代码
一、async/await优势:代码更加简洁。
修改下上面场景的代码,后续步骤须要多个返回值:
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
复制代码
用promise处理,就会发现处理返回值会比较麻烦:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
复制代码
用async/await处理:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
复制代码
二、async/await的优势:解决promise传递参数太麻烦的问题。
async/await使用try...catch处理rejected状态
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另外一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}
复制代码