Promise是什么?
JavaScript語言的一大特點(diǎn)就是單線程仔蝌,也就是說泛领,同一個(gè)時(shí)間只能做一件事。
由于這個(gè)“缺陷”敛惊,導(dǎo)致JavaScript的所有網(wǎng)絡(luò)操作渊鞋,瀏覽器事件,都必須是異步執(zhí)行瞧挤。異步執(zhí)行可以用回調(diào)函數(shù)實(shí)現(xiàn):
$.ajax({
url:'/xxx',
success:()=>{},
error: ()=>{}
})
這種方法可以清楚的讓讀代碼的人明白那一部分是Ajax請求成功的回調(diào)函數(shù)和失敗的回調(diào)函數(shù)锡宋。但是問題來了,當(dāng)一次請求需要連續(xù)請求多個(gè)接口時(shí),這段代碼仿佛進(jìn)入了一團(tuán)亂麻中:
// 第一次
$.ajax({
url:'/xxx',
success:()=>{
// 第二次
$.ajax({
url:'/xxx',
success:()=>{
// 第三次
$.ajax({
url:'/xxx',
success:()=>{
// 可能還會(huì)有
},
error: ()=>{}
})
},
error: ()=>{}
})
},
error: ()=>{}
})
再看一個(gè)例子:
每隔1秒鐘輸出遞增的數(shù)字皿伺,如( 1, 2, 3 等 )
setTimeout(function () {
console.log(1);
setTimeout(function () {
console.log(2);
setTimeout(function () {
console.log(3);
}, 1000);
}, 1000);
}, 1000);
這還是3層员辩,如果4層,5層鸵鸥。奠滑。。妒穴。宋税,理解這些代碼會(huì)很簡單,但是如果換成具體的業(yè)務(wù)邏輯需求的時(shí)候讼油,這就產(chǎn)生了回調(diào)地獄杰赛,即代碼層層嵌套,環(huán)環(huán)相扣矮台,很明顯乏屯,邏輯稍微復(fù)雜一些根时,這樣的程序就會(huì)變得難以維護(hù)。
那es6的Promise它可以幫你非常靈活的調(diào)整辰晕。
它不是新的語法功能蛤迎,而是一種新的寫法,允許將回調(diào)函數(shù)的橫向加載含友,改成縱向加載替裆。
Promise是一種對異步操作的封裝,主流的規(guī)范是Promise/A+窘问。
Promise可以使得異步代碼層次清晰辆童,便于理解,且更加容易維護(hù)惠赫。
Promise提供一個(gè)then把鉴,來為異步提供回調(diào)函數(shù):
$.ajax({
url:'/xxx',
}).then( ()=>{
// 成功的回調(diào)
}, ()=>{
// 失敗的回調(diào)
})
function next( n ){
return new Promise( function( resolve, reject ){
setTimeout( function(){
resolve( n );
}, 1000 );
} );
}
next( 1 ).then( function( res ){
console.log( res );
return next( 2 );
} ).then( function( res ){
console.log( res );
return next( 3 );
} ).then( function( res ){
console.log( res );
} )
它的先進(jìn)之處是,可以在then方法中繼續(xù)寫Promise對象并返回儿咱,然后繼續(xù)調(diào)用then來進(jìn)行回調(diào)操作纸镊。
Promise的用法
下面讓我們看看Promise是怎么使用的。首先概疆,Promise是一個(gè)對象,因此峰搪,我們使用new的方式創(chuàng)建一個(gè)岔冀。然后給它傳一個(gè)函數(shù)作為參數(shù),這個(gè)函數(shù)呢也有兩個(gè)參數(shù)概耻,一個(gè)叫resolve(決定)使套,一個(gè)叫reject(拒絕),這兩個(gè)參數(shù)也是函數(shù)鞠柄。緊接著侦高,我們使用then里調(diào)用這個(gè)Promise
const fn = new Promise(function (resolve, reject) {
setTimeout(()=>{
let num = Math.ceil(Math.random() * 10) // 假設(shè)num為7
if (num > 5) {
resolve(num) //返回7
} else {
reject(num)
}
},2000)
})
fn.then((res)=>{
console.log(res) // 7
},(err)=>{
console.log(err)
})
這就是最簡單的Promise的使用。假設(shè)2s后生成隨機(jī)數(shù)7厌杜,因此resolve回調(diào)函數(shù)運(yùn)行奉呛,then走第一個(gè)函數(shù),console.log(7)夯尽。假設(shè)2s后生成的隨機(jī)數(shù)是3瞧壮,此時(shí)reject回調(diào)函數(shù)運(yùn)行,then走第二個(gè)函數(shù)匙握,console.log(3)咆槽。
上面說了Promise的先進(jìn)之處在于可以再then方法中繼續(xù)寫Promise對象并返回,然后繼續(xù)調(diào)用then來進(jìn)行回調(diào)操作:
fn = new Promise(function (resolve, reject) {
let num = Math.ceil(Math.random() * 10)
if (num > 5) {
resolve(num)
} else {
reject(num)
}
})
// 第一次回調(diào)
fn.then((res)=>{
console.log(`res==>${res}`)
return new Promise((resolve,reject)=>{
if(2*res>15){
resolve(2*res)
}else{
reject(2*res)
}
})
}圈纺,(err)=>{
console.log(`err==>${err}`)
}).then((res)=>{ // 第二次回調(diào)
console.log(res)
}秦忿,(err)=>{
console.log(`err==>${err}`)
})
這就可以代替了上面類似es5時(shí)代jQuery的success的嵌套式的回調(diào)地獄的產(chǎn)生麦射,讓代碼清爽了許多。這里的resolve就相當(dāng)于以前的success灯谣。
Promise的原理
在Promise的內(nèi)部潜秋,有一個(gè)狀態(tài)管理器的存在,有三種狀態(tài):pending(進(jìn)行中)酬屉、fulfilled(已完成)半等、rejected(已拒絕)。
1.Promise對象初始化狀態(tài)為pending
2.當(dāng)調(diào)用resolve(成功)呐萨,會(huì)由pending => fulfilled
3.當(dāng)調(diào)用reject(失敗)杀饵,會(huì)由pending => rejected
因此,看上面的代碼中的resolve(num)其實(shí)是將promise的狀態(tài)由pengding改為fulfilled,然后向then的成功回調(diào)函數(shù)傳值谬擦,reject反之切距。但是需要記住的是注意promise狀態(tài)只能由pending => fulfilled/rejected, 一旦修改就不能再變。
當(dāng)狀態(tài)為fulfilled(rejected反之)時(shí)惨远,then的成功回調(diào)函數(shù)會(huì)被調(diào)用谜悟,并接受上面?zhèn)鱽淼膎um,進(jìn)而進(jìn)行操作北秽。promise.then方法每次調(diào)用葡幸,都返回一個(gè)新的promise對象,所以可以鏈?zhǔn)綄懛ǎo論resolve還是reject都是這樣)
Promise的幾種方法
我們先用console.dir(Promise)打印出來看看:
從上圖可以看出主要有以下幾個(gè)方法:resolve,reject,then,catch,all,race贺氓。下面分別來說說這幾個(gè)方法
resolve蔚叨、reject
1.resolve:該方法可以使Promise對象的狀態(tài)改變成成功,同時(shí)傳遞一個(gè)參數(shù)用于后續(xù)成功后的操作
2.reject: 該方法則是將Promise對象的狀態(tài)改變?yōu)槭≌夼啵瑫r(shí)將錯(cuò)誤的信息傳遞到后續(xù)錯(cuò)誤處理的操作蔑水。
Promise.resolve返回一個(gè)fulfilled狀態(tài)的promise對象,Promise.reject返回一個(gè)rejected狀態(tài)的promise對象扬蕊。
Promise.resolve('hello').then(function(value){
console.log(value);
});
Promise.resolve('hello');
// 相當(dāng)于
const promise = new Promise(resolve => {
resolve('hello');
});
// reject反之
then
then:所有的Promise對象實(shí)例都有一個(gè)then方法搀别,它是用來跟這個(gè)Promise進(jìn)行交互的,then方法主要傳入兩個(gè)方法作為參數(shù)尾抑,一個(gè)resolve函數(shù)歇父,一個(gè) reject函數(shù),鏈?zhǔn)秸{(diào)用蛮穿,上一個(gè)Promise對象變?yōu)閞esolved的時(shí)候庶骄,調(diào)用then中的Resolve方法,否則調(diào)用Reject方法践磅。
then方法用于注冊當(dāng)狀態(tài)變?yōu)閒ulfilled或者reject時(shí)的回調(diào)函數(shù):
// onFulfilled 是用來接收promise成功的值
// onRejected 是用來接收promise失敗的原因
promise.then(onFulfilled, onRejected);
需要注意的地方是then方法是異步執(zhí)行的单刁。
// resolve(成功) onFulfilled會(huì)被調(diào)用
const promise = new Promise((resolve, reject) => {
resolve('fulfilled'); // 狀態(tài)由 pending => fulfilled
});
promise.then(result => { // onFulfilled
console.log(result); // 'fulfilled'
}, reason => { // onRejected 不會(huì)被調(diào)用
})
// reject(失敗) onRejected會(huì)被調(diào)用
const promise = new Promise((resolve, reject) => {
reject('rejected'); // 狀態(tài)由 pending => rejected
});
promise.then(result => { // onFulfilled 不會(huì)被調(diào)用
}, reason => { // onRejected
console.log(rejected); // 'rejected'
})
catch
catch在鏈?zhǔn)綄懛ㄖ锌梢圆东@前面then中發(fā)送的異常。
catch: 該方法是then(onFulfilled,onRejected)方法當(dāng)中onRejected函數(shù)的一個(gè)簡單的寫法,可以理解為promise.then(undefined, onRejected)羔飞,但是用來捕獲異常時(shí)肺樟,用catch更多便于理解。
fn = new Promise(function (resolve, reject) {
let num = Math.ceil(Math.random() * 10)
if (num > 5) {
resolve(num)
} else {
reject(num)
}
})
fn.then((res)=>{
console.log(res)
}).catch((err)=>{
console.log(`err==>${err}`)
})
其實(shí)逻淌,catch相當(dāng)于then(null,onRejected),前者只是后者的語法糖而已么伯。
all
從字面意思上理解,可能為一個(gè)狀態(tài)全部怎么樣的意思卡儒,讓看下它的用法就可以明白這個(gè)靜態(tài)方法:
var p1 = Promise.resolve(1),
p2 = Promise.reject(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then((res)=>{
//then方法不會(huì)被執(zhí)行
console.log(results);
}).catch((err)=>{
//catch方法將會(huì)被執(zhí)行田柔,輸出結(jié)果為:2
console.log(err);
});
all: 該方法可以接收一個(gè)元素為Promise對象的數(shù)組作為參數(shù),當(dāng)這個(gè)數(shù)組里面所有的Promise對象都變?yōu)閞esolve時(shí)骨望,該方法才會(huì)返回硬爆。就是全部都執(zhí)行完了才接著往下執(zhí)行。
當(dāng)這幾個(gè)作為參數(shù)的函數(shù)的返回狀態(tài)為fulfilled時(shí)擎鸠,至于輸出的時(shí)間就要看誰跑的慢了:
let p1 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('1s') //1s后輸出
resolve(1)
},1000)
})
let p10 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('10s') //10s后輸出
resolve(10)
},10000)
})
let p5 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('5s') //5s后輸出
resolve(5)
},5000)
})
Promise.all([p1, p10, p5]).then((res)=>{
console.log(res); // 最后輸出
})
這段代碼運(yùn)行時(shí)缀磕,根據(jù)看誰跑的慢的原則,則會(huì)在10s之后輸出[1,10,5]劣光。
race
promise.race()方法也可以處理一個(gè)promise實(shí)例數(shù)組但它和promise.all()不同袜蚕,從字面意思上理解就是競速,那么理解起來上就簡單多了绢涡,也就是說在數(shù)組中的元素實(shí)例那個(gè)率先改變狀態(tài)牲剃,就向下傳遞誰的狀態(tài)和異步結(jié)果。但是雄可,其余的還是會(huì)繼續(xù)進(jìn)行的颠黎。
race: 競速,類似all方法滞项,它同樣接收一個(gè)數(shù)組,不同的是只要該數(shù)組中的Promise對象的狀態(tài)發(fā)生變化(無論是resolve還是reject)該方法都會(huì)返回夭坪。就是只要某一個(gè)執(zhí)行完了就接著往下執(zhí)行文判。
let p1 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('1s') //1s后輸出
resolve(1)
},1000)
})
let p10 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('10s') //10s后輸出
resolve(10) //不傳遞
},10000)
})
let p5 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('5s') //5s后輸出
resolve(5) //不傳遞
},5000)
})
Promise.race([p1, p10, p5]).then((res)=>{
console.log(res); // 最后輸出
})
運(yùn)行結(jié)果:
我們可以根據(jù)race這個(gè)屬性做超時(shí)的操作:
//請求某個(gè)圖片資源
let requestImg = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
});
//延時(shí)函數(shù),用于給請求計(jì)時(shí)
let timeOut = new Promise(function(resolve, reject){
setTimeout(function(){
reject('圖片請求超時(shí)');
}, 5000);
});
Promise.race([requestImg, timeout]).then((res)=>{
console.log(res);
}).catch((err)=>{
console.log(err);
});
Promise相關(guān)的面試題
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
reject('error')
}, 1000)
})
promise.then((res)=>{
console.log(res)
},(err)=>{
console.log(err)
})
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
setTimeout(()=>{
console.log('setTimeout')
})
let p1 = new Promise((resolve)=>{
console.log('Promise1')
resolve('Promise2')
})
p1.then((res)=>{
console.log(res)
})
console.log(1)
Promise.resolve(1)
.then((res) => {
console.log(res);
return 2;
})
.catch((err) => {
return 3;
})
.then((res) => {
console.log(res);
});
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('開始');
resolve('success');
}, 5000);
});
const start = Date.now();
promise.then((res) => {
console.log(res, Date.now() - start);
});
promise.then((res) => {
console.log(res, Date.now() - start);
});
let p1 = new Promise((resolve,reject)=>{
let num = 6
if(num<5){
console.log('resolve1')
resolve(num)
}else{
console.log('reject1')
reject(num)
}
})
p1.then((res)=>{
console.log('resolve2')
console.log(res)
},(rej)=>{
console.log('reject2')
let p2 = new Promise((resolve,reject)=>{
if(rej*2>10){
console.log('resolve3')
resolve(rej*2)
}else{
console.log('reject3')
reject(rej*2)
}
})
return p2
}).then((res)=>{
console.log('resolve4')
console.log(res)
},(rej)=>{
console.log('reject4')
console.log(rej)
})
總結(jié)
首先室梅,Promise是一個(gè)對象戏仓,如同其字面意思一樣,代表了未來某時(shí)間才會(huì)知道結(jié)果的時(shí)間亡鼠,不受外界因素的印象赏殃。Promise一旦觸發(fā),其狀態(tài)只能變?yōu)閒ulfilled或者rejected间涵,并且已經(jīng)改變不可逆轉(zhuǎn)仁热。Promise的構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該參數(shù)函數(shù)的兩個(gè)參數(shù)分別為resolve和reject勾哩,其作用分別是將Promise的狀態(tài)由pending轉(zhuǎn)化為fulfilled或者rejected抗蠢,并且將成功或者失敗的返回值傳遞出去举哟。then有兩個(gè)函數(shù)作為Promise狀態(tài)改變時(shí)的回調(diào)函數(shù),當(dāng)Promise狀態(tài)改變時(shí)接受傳遞來的參數(shù)并調(diào)用相應(yīng)的函數(shù)迅矛。then中的回調(diào)的過程為異步操作妨猩。catch方法是對.then(null,rejectFn)的封裝(語法糖),用于指定發(fā)生錯(cuò)誤時(shí)的回掉函數(shù)秽褒。一般來說壶硅,建議不要再then中定義rejected狀態(tài)的回調(diào)函數(shù),應(yīng)該使用catch方法代替销斟。all和race都是競速函數(shù)庐椒,all結(jié)束的時(shí)間取決于最慢的那個(gè),其作為參數(shù)的Promise函數(shù)一旦有一個(gè)狀態(tài)為rejected票堵,則總的Promise的狀態(tài)就為rejected扼睬;而race結(jié)束的時(shí)間取決于最快的那個(gè),一旦最快的那個(gè)Promise狀態(tài)發(fā)生改變悴势,那個(gè)其總的Promise的狀態(tài)就變成相應(yīng)的狀態(tài)窗宇,其余的參數(shù)Promise還是會(huì)繼續(xù)進(jìn)行的。
擴(kuò)展
在es7時(shí)代特纤,也出現(xiàn)了await/async的異步方案
await/async來說是基于promise的军俊,
async-await是promise和generator的語法糖。只是為了讓我們書寫代碼時(shí)更加流暢捧存,當(dāng)然也增強(qiáng)了代碼的可讀性粪躬。簡單來說:async-await 是建立在 promise機(jī)制之上的,并不能取代其地位昔穴。
老朋友Ajax
// 獲取產(chǎn)品數(shù)據(jù)
ajax('products.json', (products) => {
console.log('AJAX/products >>>', JSON.parse(products));
// 獲取用戶數(shù)據(jù)
ajax('users.json', (users) => {
console.log('AJAX/users >>>', JSON.parse(users));
// 獲取評論數(shù)據(jù)
ajax('products.json', (comments) => {
console.log('AJAX/comments >>>', JSON.parse(comments));
});
});
});
不算新的朋友promise
// Promise
// 封裝 Ajax镰官,返回一個(gè) Promise
function requestP(url) {
return new Promise(function(resolve, reject) {
ajax(url, (response) => {
resolve(JSON.parse(response));
});
});
}
// 獲取產(chǎn)品數(shù)據(jù)
requestP('products.json').then((products) => {
console.log('Promises/products >>>', products);
// 獲取用戶數(shù)據(jù)
return requestP('users.json');
}).then((users) => {
console.log('Promises/users >>>', users);
// 獲取評論數(shù)據(jù)
return requestP('comments.json');
}).then((comments) => {
console.log('Promises/comments >>>', comments);
});
碉堡的朋友 await/async
// 封裝 Ajax,返回一個(gè) Promise
function requestP(url) {
return new Promise(function(resolve, reject) {
ajax(url, (response) => {
resolve(JSON.parse(response));
});
});
}
(async () => {
// 獲取產(chǎn)品數(shù)據(jù)
let data = await requestP('products.json');
// 獲取用戶數(shù)據(jù)
let users = await requestP('users.json');
// 獲取評論數(shù)據(jù)
let products = await requestP('comments.json');
console.log('ES7 Async/products >>>', products);
console.log('ES7 Async/users >>>', users);
console.log('ES7 Async/comments >>>', comments);
}());
與Fetch Api相結(jié)合使用
(async () => {
// Async/await using the fetch API
try {
// 獲取產(chǎn)品數(shù)據(jù)
let products = await fetch('products.json');
// Parsing products
let parsedProducts = await products.json();
// 獲取用戶數(shù)據(jù)
let users = await fetch('users.json');
// Parsing users
let parsedUsers = await users.json();
// 獲取評論數(shù)據(jù)
let comments = await fetch('comments.json');
// Parsing comments
let parsedComments = await comments.json();
console.log('ES7 Async+fetch/products >>>', parsedProducts);
console.log('ES7 Async+fetch/users >>>', parsedUsers);
console.log('ES7 Async+fetch/comments >>>', parsedComments);
} catch (error) {
console.log(error);
}
}());
再次結(jié)合Fetch
(async () => {
let parallelDataFetch = await* [
(await fetch('products.json')).json(),
(await fetch('users.json')).json(),
(await fetch('comments.json')).json()
];
console.log('Async parallel+fetch >>>', parallelDataFetch);
}());
使用 await/async 用同步的思維去解決異步的代碼
詳細(xì)了解可參考:
http://www.ruanyifeng.com/blog/2015/05/async.html