var p = new Promise((resolve,reject)=>{resolve(1)})
.then((res)=>{console.log(res)}); // 1複製代碼
p是Promise的實例,那麼p的原型鏈咱們看一下,javascript
p.__proto__.__proto__.__proto__===null複製代碼
p實例是個啥?java
Object.prototype.toString.call(p); // "[object Promise]"複製代碼
p是一個promise 實例對象,爲普通的對象有什麼區別?ios
咱們遍歷一下這個p上的key,value都有啥?ajax
如下咱們與普通對象o作對比編程
var p = new Promise((resolve,reject)=>{resolve(1)})
.then((res)=>{console.log(res)}); // 1 var o = {a:1,b:2}複製代碼
for(var key in p){
console.log(key,p[key])
}
//undefined
for(var key in o){
console.log(key,o[key])
}
// a 1
// b 2複製代碼
Object.getOwnPropertyNames(p); // []
Object.getOwnPropertyNames(o); // ["a", "b"]
複製代碼
Object.keys(p); // []
Object.keys(o); // ["a", "b"]
複製代碼
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
對象。
json
所謂Promise
,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。
axios
@Promise容器包裝的異步函數有三個狀態:segmentfault
@一旦狀態改變,就不會再變, 好比,用Promise包裝的一個ajax請求數組
簡單的說就是 解決回調地域的寫法,用then鏈式調用來取代層層嵌套的回調的寫法。
promise
好比,有三個異步函數 a,b,c,b依賴a異步函數執行後的數據,c依賴異步函數b的執行結果
那麼,咱們可能就只能用嵌套的寫法去寫, 以下代碼:
async a(()=>{
var dataA = "異步函數b依賴的數據"
async b((dataA)=>{
var dataB = "異步函數c依賴的數據"
async c((dataB)=>{
return dataA + dataB + dataC
\\......
}
}
})複製代碼
function timeout(ms,data) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, data);
});
}
timeout(1000,'dataA')
.then((value) => {
console.log(value);
return timeout(3000,value+" "+'dataB')
})
.then((value) =>{
console.log(value);
return timeout(3000,value+" "+'dataC')
}).then((value)=>{
console.log(value);
})複製代碼
function getFileByPath(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (err) {
return reject(err);
}
resolve(data);
});
});
}
getFileByPath(path.join(__dirname, './1.txt'))
.then(function(data) {
console.log(data);
return getFileByPath(path.join(__dirname, './2.txt'));
}).then(function(data) {
console.log(data);
return getFileByPath(path.join(__dirname, './3.txt'));
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err.message);
});複製代碼
ES6 規定,Promise
對象是一個構造函數,用來生成Promise
實例。
const p = new Promise((resolve,reject)=>{
//... do something
if(/*成功*/){
resolve(data)
}else{
reject(err)
}
}).then((data)=>{
console.log(data);//拿到resolve參數中的data
}).catch((err)=>{
console.log(err)
throw new Error('err')
})複製代碼
Promise
構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。
resolve
函數的做用是,將Promise
對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject
函數的做用是,將Promise
對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。
Promise
實例生成之後,能夠用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
複製代碼
then
方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise
對象的狀態變爲resolved
時調用,第二個回調函數是Promise
對象的狀態變爲rejected
時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受Promise
對象傳出的值做爲參數。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
複製代碼
上面代碼中,Promise 新建後當即執行,因此首先輸出的是Promise
。而後,then
方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行,因此resolved
最後輸出。
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);
});
複製代碼
上面代碼中,getJSON
是對 XMLHttpRequest 對象的封裝,用於發出一個針對 JSON 數據的 HTTP 請求,而且返回一個Promise
對象。須要注意的是,在getJSON
內部,resolve
函數和reject
函數調用時,都帶有參數。
若是調用resolve
函數和reject
函數時帶有參數,那麼它們的參數會被傳遞給回調函數。reject
函數的參數一般是Error
對象的實例,表示拋出的錯誤;resolve
函數的參數除了正常的值之外,還多是另外一個 Promise 實例( 好比 resolve(new Promise()) ), 這種場景不多,先無論。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
複製代碼
上面代碼中,調用resolve(1)
之後,後面的console.log(2)
仍是會執行,而且會首先打印出來。這是由於當即 resolved 的 Promise 是在本輪事件循環的末尾執行,老是晚於本輪循環的同步任務。
通常來講,調用resolve
或reject
之後,Promise 的使命就完成了,後繼操做應該放到then
方法裏面,而不該該直接寫在resolve
或reject
的後面。因此,最好在它們前面加上return
語句,這樣就不會有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 後面的語句不會執行
console.log(2);
})複製代碼
Promise 實例具備then
方法,也就是說,then
方法是定義在原型對象Promise.prototype
上的。它的做用是爲 Promise 實例添加狀態改變時的回調函數。前面說過,then
方法的第一個參數是resolved
狀態的回調函數,第二個參數(可選)是rejected
狀態的回調函數。
then
方法返回的是一個新的Promise
實例(注意,不是原來那個Promise
實例)。所以能夠採用鏈式寫法,即then
方法後面再調用另外一個then
方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});複製代碼
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
console.log('發生錯誤!', error);
});
複製代碼
上面代碼中,getJSON
方法返回一個 Promise 對象,若是該對象狀態變爲resolved
,則會調用then
方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變爲rejected
,就會調用catch
方法指定的回調函數,處理這個錯誤。另外,then
方法指定的回調函數,若是運行中拋出錯誤,也會被catch
方法捕獲。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同於
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));複製代碼
promise.catch
模擬的是catch
代碼塊。
catch
方法返回的仍是一個 Promise 對象,所以後面還能夠接着調用then
方法。
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
複製代碼
上面代碼中,promise
拋出一個錯誤,就被catch
方法指定的回調函數捕獲。注意,上面的寫法與下面兩種寫法是等價的。
// 寫法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 寫法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
複製代碼
比較上面兩種寫法,能夠發現reject
方法的做用,等同於拋出錯誤。
通常來講,不要在then
方法裏面定義 Reject 狀態的回調函數(即then
的第二個參數),老是使用catch
方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});複製代碼
上面代碼中,第二種寫法要好於第一種寫法,理由是第二種寫法能夠捕獲前面then
方法執行中的錯誤,也更接近同步的寫法(try/catch
)。所以,建議老是使用catch
方法,而不使用then
方法的第二個參數。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯,由於x沒有聲明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123複製代碼
上面代碼中,someAsyncThing
函數產生的 Promise 對象,內部有語法錯誤。瀏覽器運行到這一行,會打印出錯誤提示ReferenceError: x is not defined
,可是不會退出進程、終止腳本執行,2 秒以後仍是會輸出123
。這就是說,Promise 內部的錯誤不會影響到 Promise 外部的代碼,通俗的說法就是「Promise 會吃掉錯誤」。
finally
方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});複製代碼
上面代碼中,無論promise
最後的狀態,在執行完then
或catch
指定的回調函數之後,都會執行finally
方法指定的回調函數。
finally
本質上是then
方法的特例。
promise
.finally(() => {
// 語句
});
// 等同於
promise
.then(
result => {
// 語句
return result;
},
error => {
// 語句
throw error;
}
);複製代碼
上面代碼中,若是不使用finally
方法,一樣的語句須要爲成功和失敗兩種狀況各寫一次。有了finally
方法,則只須要寫一次。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
複製代碼
上面代碼中,無論前面的 Promise 是fulfilled
仍是rejected
,都會執行回調函數callback
從上面的實現還能夠看到,finally
方法老是會返回原來的值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})複製代碼
Promise.all()
方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
複製代碼
上面代碼中,Promise.all()
方法接受一個數組做爲參數,p1
、p2
、p3
都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()
方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
有時須要將現有對象轉爲 Promise 對象,Promise.resolve()
方法就起到這個做用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
複製代碼
上面代碼將 jQuery 生成的deferred
對象,轉爲一個新的 Promise 對象。
Promise.resolve()
等價於下面的寫法。
Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
複製代碼
Promise.resolve
方法的參數分紅四種狀況。
(1)參數是一個 Promise 實例
若是參數是 Promise 實例,那麼Promise.resolve
將不作任何修改、原封不動地返回這個實例。
(2)參數是一個thenable
對象
thenable
對象指的是具備then
方法的對象,好比下面這個對象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
複製代碼
Promise.resolve
方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable
對象的then
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
複製代碼
上面代碼中,thenable
對象的then
方法執行後,對象p1
的狀態就變爲resolved
,從而當即執行最後那個then
方法指定的回調函數,輸出 42。
(3)參數不是具備then
方法的對象,或根本就不是對象
若是參數是一個原始值,或者是一個不具備then
方法的對象,則Promise.resolve
方法返回一個新的 Promise 對象,狀態爲resolved
。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello複製代碼
上面代碼生成一個新的 Promise 對象的實例p
。因爲字符串Hello
不屬於異步操做(判斷方法是字符串對象不具備 then 方法),返回 Promise 實例的狀態從一輩子成就是resolved
,因此回調函數會當即執行。Promise.resolve
方法的參數,會同時傳給回調函數。
(4)不帶有任何參數
Promise.resolve()
方法容許調用時不帶參數,直接返回一個resolved
狀態的 Promise 對象。
因此,若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用Promise.resolve()
方法。
const p = Promise.resolve();
p.then(function () {
// ...
});
複製代碼
上面代碼的變量p
就是一個 Promise 對象。
須要注意的是,當即resolve()
的 Promise 對象,是在本輪「事件循環」(event loop)的結束時執行,而不是在下一輪「事件循環」的開始時。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
複製代碼
上面代碼中,setTimeout(fn, 0)
在下一輪「事件循環」開始時執行,Promise.resolve()
在本輪「事件循環」結束時執行,console.log('one')
則是當即執行,所以最早輸出。
console.log(1)
Promise.resolve(console.log(2));
console.log(3)
// 1
// 2
// 3複製代碼
Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
複製代碼
上面代碼生成一個 Promise 對象的實例p
,狀態爲rejected
,回調函數會當即執行。
注意,Promise.reject()
方法的參數,會原封不動地做爲reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
複製代碼
上面代碼中,Promise.reject
方法的參數是一個thenable
對象,執行之後,後面catch
方法的參數不是reject
拋出的「出錯了」這個字符串,而是thenable
對象。
咱們能夠將圖片的加載寫成一個Promise
,一旦加載完成,Promise
的狀態就發生變化。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};複製代碼
axios返回的就是一個promise對象 爲何有不少人仍是會promise再封裝一次呢?
function myGet(url, params) {
return new Promise((resolve, reject) => {
axios.get(url, params).then(function (response) {
resolve(response.data)
})
.catch(function (err) {
reject(err)
})
})
}
myGet(url,params).then(function(data){console.log(data)}).catch(function(){})
複製代碼
參考:segmentfault.com/q/101000001…
結論:axios到底沒有必要在包一層promise
var fn = function(num) {
return new Promise(function(resolve, reject) {
if (typeof num == 'number') {
resolve(num);
} else {
reject('TypeError');
}
})
}
fn(2).then(function(num) {
console.log('first: ' + num);
return num + 1;
})
.then(function(num) {
console.log('second: ' + num);
return num + 1;
})
.then(function(num) {
console.log('third: ' + num);
return num + 1;
});
// 輸出結果
first: 2
second: 3
third: 4複製代碼
resolve
函數和reject
函數時帶有參數,那麼它們的參數會被傳遞給then中的回調函數。var XHR = new XMLHttpRequest()
XHR.__proto__.__proto__.__proto__.__proto__.__proto__ === null ;// true
複製代碼
感謝阮一峯老師的付出,本文主要內容來源於阮一峯老師博客,特此說明