Promise教程及用法

鵬多多發表於2021-08-16

1,介紹

Promise是非同步程式設計的一種解決方案,比回撥函式和事件更合理且更強大。可以理解為一個容器,裡面儲存著某個未來才會結束的事件的結果。

2,特點

  • Promise物件有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗),該狀態不受外界影響。

  • 狀態可以從pending變為fulfilled,或者從pending變為rejected。一旦狀態改變,就不會再變。

3,缺點

  • Promise一旦新建它就會立即執行,無法中途取消

  • 如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部

  • 當處於pending狀態時,無法得知目前的進展是剛開始還是即將完成

4,基本用法

ES6規定,Promise物件是一個建構函式,用來生成Promise例項,它接受一個函式作為引數,該函式的兩個函式引數分別是resolverejectresolve的作用是將Promise物件的狀態從未完成變為成功,reject函式的作用是將Promise物件的狀態從未完成變為失敗。

const promise = new Promise(function(resolve, reject) {
  if (/* 非同步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

5,then

Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式(一般都只用第一個引數,reject狀態交由catch專門處理)

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve('done'), ms);
  });
}

timeout(100).then(
	// resolve回撥
  (value) => { console.log(value) },
  // reject回撥
  (error) => { console.log(error) }
);

Promise新建後就會立即執行,但是then方法是微任務,將在當前指令碼所有同步任務執行完才會執行。如下程式碼:首先輸出的是A。然後才是then方法指定的回撥函式,所以B最後輸出。

let promise = new Promise(function(resolve, reject) {
  console.log('A');
  resolve();
});

promise.then(function() {
  console.log('B');
});

console.log('C');

// 輸出順序 A C B

then方法返回的是一個新的Promise例項(不是原來那個Promise例項)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法。

getJSON("/api")
.then(post => getJSON(post.commentURL))
.then(comments => console.log("resolve", comments))

如下程式碼:p2操作的結果是返回p1的非同步操作結果。這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態。如果p1的狀態是pending,那麼p2的回撥函式就會等待p1的狀態改變;如果p1的狀態已經是resolved或者rejected,那麼p2的回撥函式將會立刻執行。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))

6,catch

then一樣,catch()方法返回的也是一個Promise物件,因此後面還可以接著呼叫then()方法,如果沒有報錯,則會跳過catch()方法。此時,要是後面的then()方法裡面報錯,就與前面的catch()無關了

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,因為x沒有宣告
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});

7,finally

finally()方法是ES2018引入標準的。用於指定不管 Promise 物件最後狀態如何,都會執行的操作。該方法總是會返回原來的值,並且不接受任何引數,這意味著沒有辦法知道前面的 Promise狀態到底是fulfilled還是rejected。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

8,all()

Promise.all()方法用於將多個Promise例項,包裝成一個新的 Promise例項。該方法接受一個陣列作為引數(可以不是陣列,但必須具有Iterator介面,且返回的每個成員都是Promise例項)

const p = Promise.all([p1, p2, p3]);

如上程式碼,p1p2p3都是Promise例項,如果不是,就會先呼叫Promise.resolve(),將引數轉為Promise例項,再進一步處理。

注意:

  • 只有陣列裡所有的Promise都完成,Promise.all()才會完成

  • 如果陣列裡的Promise有一個失敗,Promise.all()就會失敗,第一個失敗的Promise例項會傳遞給Promise.all()的回撥

  • 如果作為引數的Promise例項,自己定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()catch方法。

9,race()

Promise.race()方法同樣是將多個Promise例項,包裝成一個新的Promise例項,引數與Promise.all()方法一樣。

const p = Promise.race([p1, p2, p3]);

race的意思是速度,和字面意思一樣,誰快,就取誰作為結果。上面程式碼中,只要p1p2p3之中有一個例項率先改變狀態,p的狀態就跟著改變。率先改變的Promise例項的返回值,就傳遞給p的回撥函式。

10,allSettled()

Promise.allSettled()方法接受一個陣列作為引數,陣列的每個成員都是一個Promise物件,並返回一個新的Promise陣列集合。

注意:只有等到引數陣列的所有Promise物件都發生狀態變更(不管是fulfilled還是rejected),返回的Promise物件才會發生狀態變更。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok')
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('no')
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok')
  }, 3000)
})

const all = Promise.allSettled([p1, p2, p3])
.then(res => {
  console.log(res)
})

11,any()

Promise.any()方法接受一個陣列作為引數,陣列的每個成員都是一個Promise物件,並返回一個新的Promise陣列集合。

注意:只要引數陣列的Promise物件有一個變成fulfilled狀態,Promise.any()就會變成fulfilled狀態。只有引數陣列裡所有Promise物件都變成rejected狀態,Promise.any()才會變成rejected狀態。

const all = Promise.any([p1, p2, p3])
.then(res => {
 	console.log(res)
})
.catch(err => {
	console.log(err)
})

12,現有物件轉為Promise物件

有時需要將現有物件轉為Promise物件,Promise.resolve()Promise.reject()就起到這個作用。

onst p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
// 輸出 Hello

或者

Promise.reject('出錯了')
.catch(e => {
  console.log(e)
})
// 輸出 出錯了

13,實戰用法

這裡列舉一些簡單的例子,還有很多用處。

13.1,小程式request

const Request = (options) =>{
  let url = baseURL + options.url;
  return new Promise((resolve, reject) => {
    wx.request({
      url,
      data: options.data || {},
      method: options.method || 'post',
      responseType: options.responseType || '',
      timeout: 15000,
      success (res) {
        if(res.statusCode === 200){
          resolve(res.data);
        }else{
          FN.Toast(res.errMsg);
        };
      },
      fail (res) {
        FN.Toast("網路開小差了");
        reject(res);
      }
    })
  })
}

13.2,圖片載入

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
}

13.3,封裝Toast

import { Message, MessageBox} from 'element-ui'

/**
* 提示框
* @param {String} text
*/
alert(text) {
  return new Promise((resolve, reject) => {
    MessageBox.alert(text, '溫馨提示', {
      confirmButtonText: '確定',
      callback: action => {
		if (action === 'confirm'){
			resolve(action)
		} else {
			reject(action)
		}
      }
    })
  })
}

14,手寫Promise

class myPromise {
  constructor(executor) {
    this.initState()
    this.initBind()
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  initState() {
    this.result = null
    this.state = 'pending'
    this.resolveCallback = []
    this.rejectCallback = []
  }
  initBind() {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }
  resolve(value) {
    if (this.state !== 'pending') return
    this.result = value
    this.state = 'success'
    while (this.resolveCallback.length) {
      this.resolveCallback.shift()(this.result)
    }
  }
  reject(value) {
    if (this.state !== 'pending') return
    this.result = value
    this.state = 'error'
    while (this.rejectCallback.length) {
      this.rejectCallback.shift()(this.result)
    }
  }
  then(onResolve, onReject) {
    onResolve = typeof onResolve === 'function' ? onResolve : res => res
    onReject = typeof onReject === 'function' ? onReject : res => res
    switch (this.state) {
      case 'pending':
        this.resolveCallback.push(onResolve.bind(this))
        this.rejectCallback.push(onReject.bind(this))
        break
      case 'success':
        onResolve(this.result)
        break
      case 'error':
        onReject(this.result)
        break
    }
  }
  catch(onReject) {
    return this.then(null, onReject)
  }
}

const fn = new myPromise((resolve, reject) => {
  setTimeout((res) => {
    resolve('成功')
  }, 2000)
})
.then(res => {
  console.log(res)
})

如果看了覺得有幫助的,我是@鵬多多,歡迎 點贊 關注 評論;END

面向百度程式設計

公眾號

weixinQRcode.png

往期文章

個人主頁

相關文章