雖然項目中一直在用到promise,雖然之前也學習過promise,可是對於promise真的是沒有很好的學以至用,有時候看到別人用promise的時候也是一臉懵逼,因此就決定花點時間再來好好研究一下promise究竟是什麼?應該怎麼樣用?es6
Promise 是異步編程的一種解決方案,使得執行異步操做變得像同步操做同樣。它能夠被當作一個容器,容器裏面是咱們沒法干預的,裏面保存着某個將來纔會結束的事件的結果。 Promise 對象用於表示一個異步操做的最終狀態(完成或失敗),以及該異步操做的結果值。編程
基本用法以下數組
let p=new Promise((resolve,reject)=>{
resolve(123)//成功時執行resolve
reject("出錯了")//失敗時執行reject
})
p.then(res=>{
console.log(res);
},error=>{
console.log(error);
})
//123 由於狀態變成resolve後就不會在改變了,因此reject不會被執行。
複製代碼
Promise 是一個構造函數, new Promise 返回一個 promise對象,Promise 對象是一個代理對象(代理一個值),被代理的值在Promise對象建立時多是未知的。構造函數接收一個excutor執行函數做爲參數, excutor有兩個函數形參resolve和reject,分別做爲異步操做成功和失敗的回調函數。但該回調函數並非當即返回最終執行結果,而是一個能表明將來出現的結果的promise對象。resolve和reject函數在調用promise對象時纔會被執行。promise
一、對象的狀態不受外界影響。 Promise對象有三種狀態(resolve,reject,pending)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。異步
二、狀態一旦發生改變,就不會再變,任什麼時候候均可以獲得這個結果。 Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。異步編程
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);//不會再執行了。由於在setTimeout外面已經執行過resolve了,promise的狀態已經改變了。
}, 0)
resolve(1);
});
p.then((arg) => {
console.log(arg);
});
//因此上面的輸出爲七、一、5
複製代碼
onFulfilled和onRejected分別是Promise實例對象 的成功和失敗狀況的回調函數。該方法返回一個新的 promise實例對象, 將以回調的返回值做爲resolve的參數。而且then方法能夠被同一個 promise 對象調用屢次。函數
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);//"Success!"
});
複製代碼
一、若是傳入的 onFulfilled 參數類型不是函數,則會在內部被替換爲(x) => x ,即原樣返回 promise 最終結果的函數。post
var p = new Promise((resolve, reject) => {
resolve('foo')
})
// 'bar' 不是函數,會在內部被替換爲 (x) => x
p.then('bar').then((value) => {
console.log(value) // 'foo'
})
//等價於
p.then(res=>{
return res;
}).then((value) => {
console.log(value) // 'foo'
})
複製代碼
二、then方法容許鏈式調用。經過return將結果傳遞到下一個then,或者經過在then方法中新建一個promise實例,以resolve(value)或者reject(value)方法向下一個then方法傳遞參數學習
//例子1
Promise.resolve("foo")
.then(function (string) {
return string;
})
.then(function (string) {
setTimeout(function () {
string += 'baz';
console.log(string + "第二次調用");//foobaz第二次調用
}, 1)
return string;
})
.then(function (string) {
console.log(string + "第三次調用");//foo第三次調用
return string;
}).then(res => {
console.log(res + "第四次調用");//foo第四次調用
});
//例子2
Promise.resolve("foo")
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string);//foobaz
}, 1)
})
複製代碼
三、若是函數拋出錯誤或返回一個rejected的Promise,則調用將返回一個rejected的Promise。this
//例子1
Promise.resolve()
.then( () => {
// 使 .then() 返回一個 rejected promise
throw 'Oh no!';
})
.then( () => {
console.log( 'Not called.' );
}, reason => {
console.error( 'onRejected function called: ', reason );
//onRejected function called: Oh no!
});
//例子2
Promise.reject()
.then( () => 99, () => 42 )
.then( solution => console.log( 'Resolved with ' + solution ) ); // Resolved with 42 //由於此時then方法接收的是上一個then方法reject方法中reject方法返回的值。
複製代碼
四、 promise 的 .then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once');//once
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start);//success 1007
})
promise.then((res) => {
console.log(res, Date.now() - start);//success 1007
})
複製代碼
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
this.then(null, onRejected)中因爲null不爲函數,因此實際執行爲
this.then(res=>{
return res
}, onRejected);
複製代碼
該方法返回一個Promise,而且處理reject的的狀況。onRejected表示當Promise 被rejected時,被調用的一個Function。當這個回調函數被調用,新 promise 將以它的返回值來resolve下一個then函數繼續被調用(正常狀況是調用onFulfilled方法,除非在catch中拋出錯誤)。
//例子1
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
throw 'oh, no!';
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');//after a catch the chain is restored
return 3;
}, function () {
console.log('Not fired due to the catch');
})
複製代碼
一、在異步函數中拋出的錯誤不會被catch捕獲到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不會執行
});
複製代碼
二、在resolve()後面拋出的錯誤會被忽略(由於 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。)
var p3 = new Promise(function(resolve, reject) {
resolve();
throw 'Silenced Exception!';
});
p3.catch(function(e) {
console.log(e); // 不會執行
});
複製代碼
三、Promise 對象的錯誤具備"冒泡"性質,會一直向後傳遞,直到被捕獲(或者被then()捕獲)爲止。也就是說,錯誤老是會被下一個catch語句捕獲。
Promise.reject(2).then((res) => {
console.log(res);
}).then().catch(res=>{
console.log(res);//2
});
Promise.reject(2).then((res) => {
console.log(res);
},(error)=>{
console.log(error);//2
}).then().catch(res=>{
console.log(res);//不會被捕獲,沒有任何輸出
})
複製代碼
四、若是使用了catch語句,而後前面的then方法並無報錯,那麼就至關於直接跳過該catch方法,下一個then方法接收上一個then方法中onFulfilled傳過來的參數。
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success"
return value;
}).catch(function(e) {
console.log(e); // 沒有任何輸出
}).then(function(value){
console.log(value);//"Success"
//就好像跳過了catch語句,實際上catch執行的是this.then(null,onrejected)
//等同於
//this.then((res)=>{
// return res;
//},onrejected)
}, function () {
console.log('Not fired due to the catch');
})
複製代碼
五、.then 或者 .catch 中 return 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch 捕獲。由於返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價於 return Promise.resolve(new Error('error!!!'))。
Promise.resolve()
.then(() => {
return new Error('error!!!')
//或者改成
//return Promise.reject(new Error('error!!!'))或者
//throw new Error('error!!!') 纔會被後面的catch語句捕捉到
})
.then((res) => {
console.log('then: ', res);//在這裏輸出
})
.catch((err) => {
console.log('catch: ', err)
})
六、.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log);
//輸出1
//實際執行語句爲
new Promise((resolve, reject) => {
resolve(1)
}).then(res => {
return res;
}).then((res) => {
return res;
}).then((res) => {
console.log(res);
})
複製代碼
Promise.prototype.finally = function (f) {
return this.then(function (value) {
return Promise.resolve(f()).then(function () {
return value;
});
}, function (err) {
return Promise.resolve(f()).then(function () {
throw err;
});
});
};
複製代碼
源碼實現以下:
Promise.al=function(promises){
return new Promise(reslove,reject){
let done=gen(promises.length;resolve);
promises.forEach((promise,i)=>{
promise.then(value=>{
done(i,value);
},reject)
})
}
}
function gen(len,resolve){
const count=0;
let values=[];
return function(i,value){
values[i] =value;
if(++count===i){
resolve(values);//將value構形成數據,最後一次調用resolve操做
}
}
}
複製代碼
操做成功(Fulfillment)
一、若是傳入的可迭代對象爲空,Promise.all 會同步地返回一個已完成(resolved)狀態的promise。
var p = Promise.all([]);
console.log(p);//Promise {<resolved>: Array(0)}
複製代碼
二、若是全部傳入的 promise 都變爲完成狀態,或者傳入的可迭代對象內沒有 promise,Promise.all 返回的 promise 異步地變爲完成。
var p = Promise.all([]);
console.log(p);
//Promise {<pending>}
複製代碼
三、在任何狀況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組,它包含全部的傳入迭代參數對象的值(也包括非 promise 值)。
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
複製代碼
操做失敗(Rejection)
一、若是傳入的 promise 中有一個失敗(rejected),Promise.all 異步地將失敗的那個結果給失敗狀態的回調函數,而無論其它 promise 是否完成。
var resolvedPromisesArray = [Promise.resolve(33), Promise.reject(44)];
var p = Promise.all(resolvedPromisesArray);
console.log(p);//Promise {<pending>}
// using setTimeout we can execute code after the stack is empty
//進入第二次循環
setTimeout(function(){
console.log('the stack is now empty');
console.log(p);//Promise {<rejected>: 44}
});
複製代碼
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
}
複製代碼
該方法返回一個 promise,一旦迭代器中的某個子promise執行了成功或失敗操做,父promise對象也會用子promise的成功返回值或失敗詳情做爲參數調用父promise綁定的相應句柄,並返回該promise對象。若是傳的迭代(iterable)是空的,則返回的 promise 將永遠等待。
let p=Promise.race([1,2]).then(value=>{
console.log(value);//1
});
let p=Promise.race([]);
console.log(p);//Promise {<pending>}
複製代碼
該方法的做用就是將現有對象轉爲 Promise 實例,而且該實例的狀態爲resolve。 該方法的源碼實現以下:
Promise.resolve (value) {
// 若是參數是MyPromise實例,直接返回這個實例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
複製代碼
參數value能夠是一個Promise對象,或者是一個thenable,也能夠就是一個字符串等常量,還能夠爲空。該方法返回一個以給定值解析後的Promise 對象。
Promise.resolve('foo')
// 等價於
return new MyPromise(resolve => resolve(value))
if (value instanceof MyPromise) {
return value
}else{
return new MyPromise(resolve => resolve(value))
}
複製代碼
一、若是這個值是個thenable(即帶有then方法),返回的promise會"跟隨"這個thenable的對象(從它們的運行結果來看,返回的就好像是這個thenable對象同樣。但實際上返回的是一個Promise對象),採用它的最終狀態(指resolved/rejected/pending/settled)。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
thenable.then(res=>{
console.log(res);//42
})
//上面的代碼能夠被解析爲
let p1=new Promise(resolve=>{
reslove(thenable);
})
p1.then(function(value) {
console.log(value); // 42
});
複製代碼
二、若是傳入的value自己就是promise對象,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
var original = Promise.resolve('我在第二行');
original.then(function(value) {
console.log('value: ' + value);//'我在第二行'
});
var cast = Promise.resolve(original);
cast.then(function(value) {
console.log('value: ' + value);//'我在第二行'
});
console.log('original === cast ? ' + (original === cast));//true
複製代碼
三、參數不是具備then方法的對象,或根本就不是對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
const p = Promise.resolve('Hello');
//等同於
const p=new Promise(resolve=>{
resolve('Hello');
})
p.then(res=>{
console.log(res) // Hello
});
複製代碼
四、不帶有任何參數。直接返回一個resolved狀態的 Promise 對象。
const p = Promise.resolve();
console.log(p);//Promise {<resolved>: undefined}
複製代碼
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s);// 出錯了
});
複製代碼
class Promise(){
constructor(executor){
this.status="pending";//初始狀態
this.value=null;//resolve的實參
this.reason=null;//reject的實參
try{
//防止發生隱式綁定 executor(this.resolve.bind(this),this.reject.bind(this));
}catch(e){
this.reject(e);
}
}
resolve(value){
let status=this.status;
if(status=="pending"){
this.status="resolve";
this.value=value;
}
}
reject(reason){
let status=this.status;
if(status=="pending"){
this.status="reject";
this.reason=reason;
}
}
}
Promise.then=(onfullFilled,onRejected)=>{
if(this.status=="resolve"){
onfullFilled(this.value);
}
if(this.status=="reject"){
onfullFilled(this.reason);
}
}
複製代碼
進階版1 考慮異步狀況
class Promise(executor){
constructor(executor){
if(typeof executor !=="function"){
throw new Error("executor必須是一個函數");
}
this.status="pending";//初始狀態
this.value=null;//resolve的實參
this.reason=null;//reject的實參
this.fullFilledArray=[];
that.rejectedArray=[];
try{
//防止發生隱式綁定
executor(this.resolve.bind(this),this.reject.bind(this));
}catch(e){
this.reject(e);
}
}
resolve(value){
let status=this.status;
if(status=="pending"){
this.status="resolve";
this.value=value;
let cb;
while (cb = this.fullFilledArray.shift()) {
cb(this.value)
}
}
}
reject(reason){
let status=this.status;
if(status=="pending"){
this.status="reject";
this.reason=reason;
let cb;
while (cb = this.rejectedArray.shift()) {
cb(this.reason)
}
}
}
}
Promise.then=(onfullFilled,onRejected){
let status=this.status;
switch(status){
case "pending":
//由於異步的緣由,在Promise的構造函數中的resolve和reject方法並不會立刻執行,等到調用this.then以後纔會去執行(`根據js的執行機制可知`)
this.fullfilledArray.push(onfullFilled);
this.rejectedArray.push(onRejected)
break;
case "resolve":
this.onfullFilled(this.value);
break;
case "reject":
this.onRejected(this.reason)
break;
}
}
複製代碼
進階版2 考慮鏈式調用
class Promise(){
constructor(executor){
if(typeof executor !=="function"){
throw new Error("executor必須是一個函數");
}
this.status="pending";//初始狀態
this.value=null;//resolve的實參
this.reason=null;//reject的實參
this.fullFilledArray=[];
this.rejectedArray=[];
try{
//防止發生隱式綁定 executor(this.resolve.bind(this),this.reject.bind(this));
}catch(e){
this.reject(e);
}
};
resolve(value){
let status=this.status;
if(status==="pending"){
this.status="resolve";
this.value=value;
let cb;
while (cb = this.fullFilledArray.shift()) {
cb(this.value)
}
}
};
reject(reason){
let status=this.status;
if(status==="pending"){
this.status="rejected";
this.reason=reason;
let cb;
while (cb = this.rejectedArray.shift()) {
cb(this.reason)
}
}
}
}
Promise.then=function(onFullFilled,onRejected){
let {status,value,reason}=this;
return new Promise((resolve,reject)=>{
let fullfilled=value=>{
try{
if(typeof onFullFilled !=="function"){
resolve(value);
}else{
let res=onFullFilled(value);
if(res instanceof Promise){
res.then(resolve,reject)
}else{
resolve(res);
}
}
}catch(e){
reject(e);
}
};
let rejected=reason=>{
try{
if(typeof onRejected !=="function"){
reject(value);
}else{
let res=onRejected(value);
if(res instanceof Promise){
res.then(resolve,reject);
}else{
resolve(res);
}
}
}catch(e){
reject(e);
}
};
swich(status){
case "panding":
this.fullFilledArray.push(fullfilled);
this.rejectedArray.push(rejected);
break;
case "resolve":
fullfilled(value);
break;
case "reject":
rejected(reason);
}
})
}
複製代碼
}
後面再複雜的狀況暫時就不考慮了。
參考連接
一、ECMAScript入門
二、Promise——MDN
三、Promise原理講解 && 實現一個Promise對象 (遵循Promise/A+規範)
四、這一次,完全弄懂 JavaScript 執行機制
五、Promise 必知必會(十道題)