promise
是異步編程的一種解決方案,比傳統的解決方案—回調函數和事件—更合理更強大。編程
所謂的promise
就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做的結果)。json
Promise
是一個對象,從它這裏能夠獲取異步操做的信息, Promise
提供統一的API,各類異步操做均可以用一樣的方法處理。數組
promise
的特色:promise
對象的狀態不受外界影響,promise
對象表明一個異步操做,有三種狀態:pending(進行中)
、Fulfilled(已成功)
、Rejected(已失敗)
。只有異步操做的結果才能夠決定當前是哪種操做狀態,任何其餘操做都沒法改變這種狀態。併發
一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果。Promise
對象的狀態改變只有兩種可能:從pending(進行中)
變爲Fulfilled(已成功)
、或者從pending(進行中)
變成Rejected(已失敗)
。狀態發生改變就凝固了,不會再變,而是一直保持這個結果,這是就稱爲Resolved(已定型)
。就算改變已經發生,再對Promise
對象添加回調函數,也會當即獲得這個結果,這個與event
徹底不一樣,事件的特色是,若是錯過了它,再去監聽是得不到結果的。app
有了promise
對象,就能夠將異步操做以同步操做表示出來,避免了層層嵌套的回調函數。異步
Promise
的缺點:async
沒法取消Promise
,一旦新建它就會當即執行,沒法中途取消。異步編程
若是不設置回調函數,Promise
內部拋出錯誤不會反應到外部。函數
當處於Pending
狀態時,沒法得知目前進展到哪個階段了。
let promise = new Promise(function(res, rej) { // your code... }) promise.then(function(value) { // res }, function(error) { // rej })
let promise = new Promise(function(resolve, reject) { // resolve, reject 纔是真正的異步 console.log('1') resolve() }) promise.then(() => { console.log('2') }) console.log('3') // 1 3 2
// 異步加載圖片 function loadImageAsync(url) { return new Promise(function(resolve, reject) { let image = new Image() iamge.onload = function () { resolve(image) } image.onerror = function() { reject(new Error('不能加載' + url)) } iamge.src = url }) }
// promise對象實現AJAX // getJSON 是對XMLHttpReqest對象的封裝,用於發出一個JSON數據的HTTP請求 // 並返回一個Promise對象。須要注意的是,在getJSON內部,resolve函數和reject函數 // 都帶有參數 let getJSON = function(url) { let promise = new Promise(function(resolve, reject) { let client = new XMLHttpRequest() client.open('GET', url) client.onreadystatechange = handler client.responseType = "json" client,setRequestHeader('Accept','application/json') client.send() function handler() { if(this.readyState !== 4) { return } if(this.status === 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } }) return promise } getJSON('/posts.json').then(function(json) { console.log('Contents:' + json) },function(error) { console.log('error',error) })
/** * 若是resolve 和 reject 函數都帶有參數,那麼這些參數會被傳遞到回調函數。 * reject函數接收Error對象的實例,表示拋出的錯誤 * reslove 函數參數除了正確的值,還有多是一個Promise對象 */ let p1 = new Promise(function(resolve, reject) { // ... }) let p2 = new Promise(function(resolve, reject) { // ... resolve(p1) }) /**p1 和 p2 都是Promise的實例,可是p2的resolve方法將p1做爲參數 * 即一個異步操做的結果是返回另外一個異步操做。 * p1的狀態傳遞給p2.p1的狀態決定了p2的狀態 */
let p1 = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)).catch(error => console.log(error)) /** * p1 是 一個Promise,3s以後變成rejected。 * p2 的狀態在1s後改變,resolve方法返回的是p1 */
promise.then(function(value) { // res }, function(error) { // rej }) // then方法有兩個參數,第一個參數是Resolved狀態的回調函數,第二個參數(可選)是Rejected狀態的回調函數。 // then返回的是一個新的Promise實例
Promise.prototype.catch
方法是.then(null, rejection)
的別名,用於指定發生錯誤的回調函數。
另外then
方法指定的回調函數若是在運行中拋出錯誤,也會被catch
捕獲。
通常來講,不要在then
p2.then(result => console.log(result)) .catch(error => console.log(error)) // .catch 處理 p2 和前一個回調函數運行時發生的錯誤 // ====> 等同於 p2.then(result => console.log(result)) .then(null, err => console.log(err))
Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]); // Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法, 將參數轉爲 Promise 實例,再進一步處理。
const databasePromise = connectDatabase(); const booksPromise = databasePromise.then(findAllBooks); const userPromise = databasePromise.then(getCurrentUser); // booksPromise和userPromise是兩個異步操做,只有等到它們的結果都返回了,纔會觸發pickTopRecommentations這個回調函數。 // 若是做爲參數的 Promise 實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()的catch方法 Promise.all([booksPromise, userPromise]).then(([books, user]) => pickTopRecommentations(books, user));
ES7標準引入了async
函數,使得異步操做變得簡單,——async
就是Generator
的語法糖。
async
函數返回一個Promise
對象,可使用then
方法添加回調函數。當函數執行的時候,
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result) { console.log(result); }); // 一、函數前面的async關鍵字代表該函數內部有異步操做。 // 二、調用該函數會當即返回一個Promise對象
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { // 一旦趕上await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。 await timeout(ms); console.log(value); } asyncPrint('hello world', 3000); // 3s以後返回 'hello world' /** ------------- 改寫 ------------------ */ async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 3000);
async
函數返回一個promise
對象,async
函數內部return語句返回的值,會成爲then
方法回調函數的參數。
async
函數返回的 Promise
對象,必須等到內部全部await
命令後面的 Promise
對象執行完,纔會發生狀態改變,除非遇到return
語句或者拋出錯誤。
也就是說,只有async
函數內部的全部異步操做執行完,纔會執行then
方法指定的回調函數。
正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。
async function f() { return await 123; } // await命令的參數是數值123,它被轉成 Promise 對象,並當即resolve。 f().then(v => console.log(v)) // 123
await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。
async function f() { await Promise.reject('出錯了'); } f().then(v => console.log(v)) .catch(e => console.log(e)) // 出錯了
只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。
async function f() { await Promise.reject('出錯了'); await Promise.resolve('hello world'); // 不會執行 } // 第二個await不會執行
async function f() { try { await Promise.reject('出錯了'); } catch (e) { console.log(e); } return await Promise.resolve('hello world'); } f().then(v => console.log(v)) // hello world
async function f() { await Promise.reject('出錯了').catch(e => console.log(e)); return await Promise.resolve('hello world'); } f().then(v => console.log(v)) // 出錯了 // hello world
若是await後面的異步操做出錯了,那麼等同於async函數返回的Promise對象被reject了。
async function f() { await new Promise(function (resolve, reject) { throw new Error('出錯了'); }); } f().then(v => console.log(v)).catch(e => console.log(e)) // Error:出錯了 // async函數f執行後,await後面的 Promise 對象會拋出一個錯誤對象, // 致使catch方法的回調函數被調用,它的參數就是拋出的錯誤對象。
// 有多個await命令,則能夠統一放在try...catch...結構中 async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } }
第一點,前面已經說過,await命令後面的Promise對象,運行結果多是rejected,因此好把await命令放在try...catch代碼塊中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另外一種寫法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err) { console.log(err); }); }
第二點,多個await命令後面的異步操做,若是不存在繼發關係,好讓它們同時觸發
// 寫法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 寫法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
第三點,await命令只能用在async函數之中,若是用在普通函數,就會報錯。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 報錯 docs.forEach(function (doc) { await db.post(doc); }); } // 若是將forEach方法的參數改爲async函數,也有問題。 function dbFuc(db) { //這裏不須要 async let docs = [{}, {}, {}]; // 可能獲得錯誤結果 docs.forEach(async function (doc) { await db.post(doc); }); } // 緣由是這時三個db.post操做將是併發執行,也就是同時執行,而不是繼發執行。正確的寫法是採用for循環。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
// 若是確實但願多個請求併發執行,可使用Promise.all方法。當三個請求都會resolved時,下面兩種寫法效果相同。 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的寫法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
var pro = new Promise(function(){ var a = 1; }) console.log(pro);
const fs = require('fs') // 總結:只要 new 了一個具體的異步操做,這個異步操做被建立的一瞬間,就會當即執行; function readFileByPath(fpath) { const p = new Promise(function() { fs.readFile(fpath, 'utf-8', (err, result) => { if (err) return console.log('讀文件失敗:' + err.message) console.log(result) }) }) } readFileByPath('./files/3.txt') // 文件必須存在
封裝原則:不要在方法內部顯示結果,要把結果返回給調用者,不要提調用者作決定!!
const fs = require('fs') function readFileByPath(fPath) { return new Promise(function(resolve,reject){ fs.readFile(fPath, 'utf-8', (err, result) => { if (err) return reject(arr) resolve(result) }) }) } // 通常.then()方法中,失敗的回調能夠省略,可是省略之後讀取文件失敗時,沒法接收結果 // 這是,咱們可使用.catch() 來指定失敗的回調 /* --------------讀一個-------------------- */ readFileByPath('./files/1.txt') .then(function(result) { console.log(result) }) .catch(err => console.log(err.message)) /* ---------------讀多個----------------------- */ readFileByPath('./files/1.txt') .then(function(result){ console.log(result) return readFileByPath('./files/2.txt') }) .then(function(result){ console.log(result) return readFileByPath('./files/3.txt') }) .catch(err => console.log(err.message))
const fs = require('fs') function readFileByPath(fPath) { return new Promise(function(resolve,reject){ fs.readFile(fPath, 'utf-8', (err, result) => { if (err) return reject(arr) resolve(result) }) }) } // async 用來修飾異步方法 // await 只能用在被 async 修飾的方法中 // 同時,await 是用來修飾 Promise 實例對象的;簡化promise對象 console.log("開始"); async function readAll () { console.log("方法頭部"); const result1 = await readFileByPath('./files/1.txt') console.log(result1) const result2 = await readFileByPath('./files/2.txt') console.log(result2) const result3 = await readFileByPath('./files/3.txt') console.log(result3) console.log("方法尾部"); } console.log("結束"); readAll()
運行順序:
緣由: