javascript中Promise使用詳解

前言:

做過前端開發的都知道,JavaScript是單線程語言,瀏覽器只分配給JS一個主線程,用來執行任務,但是每次一次只能執行一個任務,這些任務形成一個任務隊列排隊等候執行;但是某些任務是比較耗時的,如網絡請求,事件的監聽,以及定時器,如果讓這些非常耗時的任務一一排隊等候執行,那麼程序執行效率會非常的低,甚至會導致頁面假死。因此,瀏覽器爲這些耗時的任務開闢了新的線程,主要包括http請求線程、瀏覽器事件觸發線程、瀏覽器定時觸發器,但是這些任務都是異步的,這就涉及到了前端開發的異步回調操作處理,前端處理異步回調操作用到的就是Async/Await和Promise。

而且在前端相關的面試的時候,面試官一般都會問到關於Promise相關的使用問題,甚至在筆試中也會出一些關於Promise和setTimeout的執行結果,這說明Promise的使用對於前端開發來說是非常重要的一個知識點。那麼本篇博文就來分享一下關於Promise的使用相關的知識點。

 

一、首先,要知道爲什麼要用Promise語法?

在介紹Promise之前,首先來了解一下JavaScript的特性。搞前端開發的都知道JS是一個傳統的單線程編程,它裏面的程序運行都是同步的,只有一個主線程,但是隨着技術的發展,爲了解決前期的缺陷,引入了異步思想,也就是一個異步過程的執行將不再與原有的序列有順序關係,這就解決了同步執行引起的執行效率不高的缺陷。用一句話解釋:異步就是從主線程發射一個子線程來完成任務。

再來了解一下Promise,Promise是ES6新增加的,它是一個由ES6提供的類,其主要目的就是很好的處理複雜的異步任務,但是它不是任何瀏覽器都能支持,比如一些舊版本的瀏覽器就不支持,只有蘋果的Safari10和Windows的Edge14版本以上瀏覽器纔開始支持ES6特性的。

Promise作爲替代回調函數執行,作爲異步操作的處理方法;是JS異步執行時候,回調函數嵌套回調函數的這一問題的解決方法,Promise更簡潔地控制函數執行流程。Promise對象其實表示是一個異步操作的最終成敗,以及結果值,也就是一個代理值,是ES6中的一種異步回調解決方案。

Promise對象代理的值其實是未知的,狀態是動態可變的,因此Promise對象的狀態有三種:進行中、結束、失敗,它運行的時候,只能從進行中到失敗,或者是從進行中到成功。使用Promise對象只要是通過同步的表達形式來運行異步代碼。

  • pending:初始狀態,既不成功,也不失敗;
  • fulfilled:操作成功結束;
  • rejected:操作失敗。
Promise屬於Es 新增的內置構造函數,可以直接調用。

英文意思是:承諾

有三種狀態:pending-等待態 resolved-成功態 rejected-失敗態

new的時候傳入一個執行(器函數)

=〉1 這個執行器會立即執行

=〉2 這個執行器接受兩個函數參數 分別是resolve和rejected

=〉3 調用resolve,會把promise狀態從pending—>resolved

​ 調用reject,會把promise狀態從pending—>rejected

怎麼構造Promise?這裏簡單舉一個構造Promise的示例:

new Promise(function (resolve, reject) {   // 要做的事情...});

 

通過上面新構造一個Promise 對象好像並沒有看出它是怎樣很好的處理複雜的異步任務的,那麼接下來就是Promise的核心操作。

 

二、接着,來了解一下回調地獄(Callback Hell)

回調地獄也叫回調嵌套或者函數混亂的調用,通俗點講就是:需要發送三個網絡請求,第三個請求依賴第二個請求的結果,第二個請求依賴第一個請求的結果。不斷增加的嵌套使用。

回調函數的弊病:

開發者閱讀起來很費神、喫力,不利於排查錯誤,更不能直接return,等等。

setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
},3000)
},2000)
},1000)
 

 

三、最後,也是本章的重頭戲,Promise的基本使用

Promise 構造函數只有一個參數,是一個函數,這個函數在構造之後會直接被異步運行,所以我們稱之爲起始函數。起始函數,也就是Promise的構造函數裏面有兩個參數:resolve和reject,該兩個參數表示的是異步操作的結果,也就是Promise成功或失敗的狀態。

當 Promise 被構造時,起始函數會被異步執行;resolve 和 reject 都是函數,其中調用 resolve 代表一切正常,reject 是出現異常時所調用的。

  • ①異步操作成功,調用resolve函數,將Promise對象的狀態改爲fulfilled。
  • ②異步操作失敗,調用rejected函數,將Promise對象的狀態改爲rejected。

Promise基本用法:

創建:

const  p = new Promise((resolve, reject) => {
	console.log(123);
  resolve('我成功了');
  // reject('我失敗了');
})

then方法使用:

// promise實例的then方法(then方法是異步的),當p的狀態是成功的時候,第一個回調函數會被執行,當p的狀態是失敗的時候, 第二個回調函數會被執行
p.then(res => {
  console.log('SUCCESS',res);
},error => {
  console.log('ERROR',error);
})
console.log(456);
//最終輸出結果如下: 123     456    SUCCESS

then的鏈式調用 p.then().then()....

// 第一個then到底執行哪個回調,取決於p的狀態
// 後面的then到底執行哪個回調 取決於上一次then執行的回調函數的返回值
// => 假如上一次then返回的是普通紙,數字、字符串、對象。。。 那麼本次then執行成功回調
// => 假如上一次then返回的是Promise對象,假如該Promise對象是成功態,本次then執行成功回調,失敗態,本次then執行失敗回調
// => 上一次then回調函數執行過程中出錯,直接走下一次then的失敗回調。
p.then((r) => {
  console.log('aaa',r);
  return 1; // 不寫return默認返回undefined  return結果會作爲下一個then成功回調參數。
  // reject(); 則走下一個then的失敗回調
}).then((r) => {
  console.log('bbb',r)
})

catch方法使用:

// 失敗態的時候 會執行catch傳入的回調函數
p.catch(e => {
  console.log('Fail',e);
});

all方法使用:

// 異步併發時,可以用all方法,並且返回結果是一個數組,數組結果的順序則是請求的先後順序,不會改變。
// 並且每個請求都成功纔會返回成功結果數組,否則會報錯
const p1 = new Promise((resolve,reject) => {
  // 異步
  setTimeout( ()=> {
    resolve('success1');
  },5000);
});
const p2 = new Promise((resolve,reject) => {
  // 異步
  setTimeout( ()=> {
    resolve('success2');
  },1000);
})
const p3 = new Promise((resolve,reject) => {
  // 異步 xi
  setTimeout( ()=> {
    resolve('success3');
  },1000);
})
Promise.all([p1,p2,p3]).then((res) => {
  console.log(res); // ['success1','success2','success3']
})

async 和 await (Es7語法,Es6中已有提案)

異步流程 回調 —> promise —> generator —> async+await

// async是函數修飾關鍵字
// await 必須在async修飾的函數內部使用
// await 後面是一個promise對象纔有意義
async function fn() {
  const res = await this.getList(); // this.getList()獲取列表數據的請求函數
  console.log(res);
}

js實例:

//小明他老爸說:假設這次考試及格的話,給他買AJ,不及格,吊起來來打
//$.ajax({
//  url: "去考試",
//  success: function(res){
//      if(res >= 60){
//          console.log("給他買aj");
//      }else{
//          console.log("吊起來來打");
//      }
//  }
//});

 // 小紅明他老爸說:假設這次考試及格的話,給她買5年模擬3年考試,不及格,帶它去看鯊魚
 // $.ajax({
 //     url: "去考試",
 //     success: function(res){
 //         if(res >= 60){
 //             console.log("給她買5年模擬3年考試");
 //         }else{
 //             console.log("帶它去看鯊魚");
 //         }
 //     }
 // });
 

 function test(resolve, reject){
     $.ajax({
         url: "去考試",
         success: function(res){
             if(res >= 60){
                 resolve();
             }else{
                 reject();
             }
         }
     });
 }

 // test(function(){
 //     console.log("給他買aj");
 // }, function(){
 //     console.log("吊起來來打");
 // })

 // Promise
 var promise = new Promise(function(resolve, reject){
     setTimeout(function(){
         // 模擬語文分數
 var res = 61;

 if(res >= 60){
     resolve("小明同學語文及格");
         }else{
             reject();
         }
     }, 2000)
 })

 var promise2 = new Promise(function(resolve, reject){
     setTimeout(function(){
         // 模擬數學分數
         var res = 61;

         if(res >= 60){
             resolve();
         }else{
             reject();
         }
     }, 2000)
 })

 // 考語文
 // promise.then(function(){
 //     console.log("給他買aj");

 //     // 先考完語文,再考數學
 //     promise2.then(function(){
 //         console.log("再買一雙aj");
 //     });

 // }).catch(function(){
 //     console.log("吊起來來打");
 // })

 //使用async修飾函數,且使用await 修飾promise,用來替代promise.then().then()多次執行的問題,讓代碼更加簡潔
 async function test(){
     // 先考語文
    var res = await promise;
    console.log(res)

    // 再考數學
    var res2 = await promise2;
    console.log(res2);

    // ...其他科目考試
 }

 test();

又例如:

通過一個異步讀取文件的小栗子來對比下Promise和 async 的異同點

const fs = require('fs')

function readFile(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if(err) {
        reject(err)
      }
      resolve(data.toString())
    })
  })
}
(1)、通過 Promise 讀取文件
 
readFile('data/a.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/b.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/c.txt').then(res => console.log(res))
                      .catch(err => console.log(err))
(2)、通過 async 函數讀取文件
充分吸取了 Promise 優點,同時避免了缺點
async function read() {
  let readA = await readFile('data/a.txt')
  let readB = await readFile('data/b.txt')
  let readC = await readFile('data/c.txt')

  console.log(readA)
  console.log(readB)
  console.log(readC)
}

read()

最終的輸出結果

async 函數的多種使用形式

// 函數聲明
async function foo() {
  // ....
}

// 函數表達式
let foo = async function() {
  // ....
}

// 箭頭函數
let foo = async() => {}

// 對象的方法
let obj = {
  name: 'Roger',
  async foo() {

  }
}
obj.foo().then(res => {
  // ....
})

// 類的方法
class Student{
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  async say() {
    return `My name is ${this.name}, I'm ${this.age} years old !`
  }
}

let jim = new Student('Jim Green', 13)
jim.say().then(res => console.log(res))   // My name is Jim Green, I'm 13 years old !
async 基本用法
 
async 函數返回一個 Promise 實例對象,可以使用 then 方法添加回調函數。
 
當函數執行時,一旦遇到 await 就會先返回,等到異步操作完成,再接着執行函數體內後面的語句
// 休眠 ms 毫秒
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms)
  })
}

async function print(ms) {
  console.log('start... ...')
  await sleep(ms)
  console.log('end... ...')
}

print(1000)

(1)、async 函數內部 return語句返回的值,會成爲then方法回調函數的參數

async function foo() {
  return 'hello world'
}

foo().then(res => console.log(res))   // hello world
(2)、async 函數內部拋出錯誤,會導致返回的 Promise對象變成reject狀態,拋出的錯誤會被catch方法回調函數接收到
 
async function bar() {
  return new Error('Error... ...')
}

bar().then(res => console.log(res))
     .catch(err => console.log(err))   // Error: Error... ...
(3)、只有 async 函數內部的異步操作執行完,纔會執行 then方法指定的回調函數
 
async function baz() {
  await new Promise(resolve => {
    console.log('執行第一個異步操作')
    setTimeout(resolve, 2000)
  })

  await new Promise(resolve => {
    console.log('執行第二個異步操作')
    setTimeout(resolve, 3000)
  })

  return '異步執行完畢再執行then方法'
}

baz().then(res => {console.log(res)})

4、await 命令
await 用於等待一個 Promise對象,它只能在一個 async函數中使用
[return_value] = await expression

表達式:一個 Promise對象或者任何要等待的值

返回值:返回 Promise對象的處理結果。如果等待的不是 Promise對象,則返回該值本身

await命令會暫停當前 async函數的執行,等待 Promise處理完成。如果 Promise正常處理,其回調的 resolve函數參數會作爲 await表達式的返回值,繼續執行 async函數。如果 Promise處理異常,await表達式會把 Promise的異常原因拋出

// 如果 await 命令後的表達式的值不是一個 Promise,則返回該值本身
async function foo() {
  return await 123
}

foo().then(res => console.log(res))    // 123


// 如果 Promise 正常處理(fulfilled),其回調的resolve函數參數作爲 await 表達式的返回值
async function bar() {
  let f = await new Promise((resolve, reject) => {
    resolve('我是表達式的返回值')
  })
  console.log(f)   // 我是表達式的返回值

  return 'ending'
}

bar().then(res => {console.log(res)})    // ending


// 如果 Promise 處理異常(rejected),await 表達式會把 Promise 的異常原因拋出
async function baz() {
  await new Promise((resolve, reject) => {
    reject(new Error('出錯啦......'))
  })
}

baz().then(res => console.log(res))
     .catch(err => console.log(err))     // Error: 出錯啦......
(1)、任何一個 await語句後面的 Promise對象變爲 reject狀態,那麼整個 async函數都會中斷執行
async function foo() {
  await Promise.reject('error')
  return 'ending'   // 未執行
}

foo().then(res => console.log(res))
// Uncaught (in promise) error
(2)、如果希望當前面的異步操作失敗時,不要中斷後面的異步操作,可以把前面的 await放在try...catch結構裏面
async function foo() {
  try{
    await Promise.reject('error')
  } catch(e) {
    
  }
  return await Promise.resolve('執行完畢')
}

foo().then(res => console.log(res))    // 執行完畢
還可以在 await後面的 Promise對象再跟一個 catch方法,處理前面可能出現的錯誤
async function foo() {
  await Promise.reject('error').catch(err => console.log(err))
  return '執行完畢'
}

foo().then(res => console.log(res))    // 執行完畢
(3)、如果想讓多個異步操作同時觸發,縮短程序的執行時間,可以參考如下兩種寫法
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章