Promise構造函數那點事

new Promise的原型鏈 Promise的實例對象Promise 的含義promise的簡單使用Promise.prototype.then()Promise.prototype.catch()Promise.prototype.finally()Promise.all()Promise.resolve()Promise.reject()應用總結

new Promise的原型鏈

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}複製代碼

1. 使用for  in來遍歷一個對象

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複製代碼

2. 使用Object.getOwnPropertyNames()來獲取key名數組

Object.getOwnPropertyNames(p); // []
Object.getOwnPropertyNames(o); // ["a", "b"]
複製代碼

3. 經過Object.keys(),來看有什麼key值

Object.keys(p); // []
Object.keys(o); // ["a", "b"]
複製代碼


p這個Promise的實例對象上爲啥什麼都沒有???先保留此問題


@ 看一下Promise的原型對象上有什麼?


爲何只有catch   constructor   finally   then,那麼   all  race  try等Promise上的方法是從何而來? 先保留此問題


Promise(「承諾」) 的含義與特色與解決了什麼問題

Promise的含義

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
json

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。
axios

Promise的特色

@Promise容器包裝的異步函數有三個狀態:segmentfault

  1.  pendding
  2. resolve
  3. reject

@一旦狀態改變,就不會再變, 好比,用Promise包裝的一個ajax請求數組

  1. 要麼成功,成功後不可能在失敗
  2. 要麼失敗,失敗後也不可能在成功

解決了什麼問題

簡單的說就是 解決回調地域的寫法,用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   
            \\......      
        }    
    }
})複製代碼

用promise怎麼寫呢?

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);
})複製代碼

@另一個Promise讀取文件的例子

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);
    });複製代碼


promise的簡單使用

ES6 規定,Promise對象是一個構造函數,用來生成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構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 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對象傳出的值做爲參數。

@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最後輸出。

下面是一個用Promise對象實現的 Ajax 操做的例子。


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())  ), 這種場景不多,先無論。


注意,調用resolve或reject並不會終結 Promise 的參數函數的執行。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1
複製代碼

上面代碼中,調用resolve(1)之後,後面的console.log(2)仍是會執行,而且會首先打印出來。這是由於當即 resolved 的 Promise 是在本輪事件循環的末尾執行,老是晚於本輪循環的同步任務。

通常來講,調用resolvereject之後,Promise 的使命就完成了,後繼操做應該放到then方法裏面,而不該該直接寫在resolvereject的後面。因此,最好在它們前面加上return語句,這樣就不會有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 後面的語句不會執行
  console.log(2);
})複製代碼

Promise.prototype.then()

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()

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 會吃掉錯誤」。

Promise.prototype.finally()

finally方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});複製代碼

上面代碼中,無論promise最後的狀態,在執行完thencatch指定的回調函數之後,都會執行finally方法指定的回調函數。

finally本質上是then方法的特例。

promise
.finally(() => {
  // 語句
});

// 等同於
promise
.then(
  result => {
    // 語句
    return result;
  },
  error => {
    // 語句
    throw error;
  }
);複製代碼

上面代碼中,若是不使用finally方法,一樣的語句須要爲成功和失敗兩種狀況各寫一次。有了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.all()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3]);
複製代碼

上面代碼中,Promise.all()方法接受一個數組做爲參數,p1p2p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。

p的狀態由p1p2p3決定,分紅兩種狀況。

(1)只有p1p2p3的狀態都變成fulfilledp的狀態纔會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

Promise.resolve()

有時須要將現有對象轉爲 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()

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;
  });
};複製代碼

Promise容器包裝setTimeout,去控制setTimeout的執行順序

Promise容器包裝ajax,去控制ajax的執行順序

Promise容器包裝axios,去控制axios的執行順序<--axios到底有沒有必要在包一層promise

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


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複製代碼


總結

  • Promise是一個異步函數的 容器
  • 若是調用resolve函數和reject函數時帶有參數,那麼它們的參數會被傳遞給then中的回調函數。
  •  最好在resolve()前面加上 return 語句,不然,resolve後面的代碼仍是會執行。
  • then(cb1,cb2), cb1等待上方的異步函數的狀態變爲resolved纔會執行,cb2會等上方的異步函數變爲reject時會執行。
  • Promise 會吃掉錯誤
  • xhr的原型鏈 

var XHR = new XMLHttpRequest()
XHR.__proto__.__proto__.__proto__.__proto__.__proto__ === null ;// true
複製代碼


感謝阮一峯老師的付出,本文主要內容來源於阮一峯老師博客,特此說明

相關文章
相關標籤/搜索