首先先理解如下幾個概念的概念:git
同步:一次只能執行一次任務,這個任務執行完以後才能執行下一個,它會阻塞其餘任務。es6
異步:能夠一塊兒執行多個任務。github
回調地獄:回調函數的層層嵌套,致使代碼層次過多,很差理解和維護。面試
先簡單瞭解如下Promise的含義和特性:編程
Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更加合理和強大;segmentfault
所謂的Promise,簡單來講就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果;數組
從語法上來講,Promise是一個對象,從它能夠獲取異步操做的消息;promise
Promise對象表明的是一個異步操做,有三種狀態: pending(等待狀態)、 fulfilled(成功狀態)、 rejected(失敗狀態)markdown
1. 對象的狀態不收外界的影響;併發
只有異步操做的結果才能決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
2. 一旦狀態改變就不會再變;
Promise對象狀態改變只有兩種可能:
只要這兩種狀態發現了,狀態就凝固了,不會再變了,會一直保持這個結果,這時稱爲resolved(已定型)
3. 每一個Promise實例都有一個then方法,一個Promise能夠.then多個;
4. 每次執行Promise的時候,都會返還一個新的Promise實例;
有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,解決了異步層層嵌套的函數(簡稱回調地獄)問題。
說到 Promise,咱們首先想到的最核心的功能就是異步鏈式調用。
沒法取消Promise。一旦新建它就會當即執行,沒法中途取消;
若是不設置回調函數,Promise內部拋出錯誤,不會反應到外部;
當處於Pending(進行中)狀態時,沒法得知目前進展到哪個節點(剛開始仍是即將完成);
注意,爲了行文方便,本章後面的resolved統一隻指fulfilled狀態,不包含rejected狀態。
ES6規定,Promise對象時一個構造函數,使用new來生成Promise實例。
let promise = new Promise(() => {//executor執行器,特色是當即執行
})
複製代碼
這個Promise實例是一個類,容許接受一個函數做爲參數,這個函數叫作executor執行器,特色是當即執行。以下會當即打印:
let promise = new Promise(() => {//executor執行器,做用當即執行
console.log("當即執行");//Promise有三種狀態,此處默認Pending狀態
})
console.log("222");
/*控制檯打印*/
//當即執行
//222
複製代碼
函數的能夠接受兩個參數resolve(表明成功狀態)和rejected(表明失敗狀態),並且每一個Promise實例都有一個
then方法。(注意:resolve和rejected必須配合then一塊兒使用,否則會打印空值)
then分別放置了兩個函數:
let promise = new Promise((resolve,rejected) => {
resolve('成功');//若是這裏調用的是resolve,那邊then就會走成功的邏輯;
}).then(data => {//成功
console.log(data);
},err => {//失敗
console.log(err);
})
/*控制檯打印*/
//成功
let promise = new Promise((resolve,rejected) => {
rejected('失敗');//若是這裏調用的是rejected,那邊then就會走失敗的邏輯;
}).then(data => {//成功
console.log(data);
},err => {//失敗
console.log(err);
})
// 失敗
//上邊的代碼也能夠這樣寫:
let promise = new Promise((resolve, rejected) => {
resolve('成功');
})
promise.then(data => {
console.log(data);
}, err => {
console.log(err);
})
複製代碼
由於Promise實例的狀態一旦狀態改變就不會再變,因此調用resolve以後再調用rejected,後一步是不會生效的。反之亦然。
let promise = new Promise((resolve,rejected) => {
resolve('成功');
rejected('失敗');//這一步不會執行
}).then(data => {//成功
console.log(data);
},err => {//失敗
console.log(err);
})
//成功
複製代碼
若是Promise實例內部報錯,就會變成失敗狀態,不會執行後邊的方法:
let promise = new Promise((resolve, rejected) => {//executor執行器,做用當即執行
throw new Error('內部拋出錯誤');//內部拋出錯誤,就會變成失敗狀態,then就會走失敗的邏輯
resolve('成功');//這一步依然不會執行
}).then(data => {//成功
console.log(data);
}, err => {//失敗
console.log(err);
})
//Error: 內部拋出錯誤
// ...
複製代碼
新建一個promise.js,並新建一個Promise類,並導出:
//promise.js:
class Promise {
};
module.exports = Promise;//導出
複製代碼
將前面的代碼引入promise.js:
//callBack.js:
let Promise = require('./promise');
//引入自定義的Promise,至關於下面用的Promise就是自定義的Promise
let promise = new Promise((resolve, rejected) => {
resolve('成功');
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
複製代碼
根據callBack.js的Promise實例,自定義一個基礎版的Promise,不考慮其餘異常狀況:
(1)建立公共的then方法
Promise實例有三種狀態,不論是哪種狀態都會執行的then方法,因此then方法屬於公共的屬性。
//promise.js:
class Promise {
then(){
}
};
module.exports = Promise;//導出
複製代碼
每個Promise實例都有本身的三個狀態,因此將三種狀態放到對應的構造函數(constructor)中。
在構造函數中能夠拿到Promise實例的狀態,由於這個三個狀態會常常用到,因此咱們能夠把它們存成一個常量。
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
}
then(){
}
};
module.exports = Promise;//導出
複製代碼
咱們在callBacks.js的new Promise實例時,傳了一個函數(也就是一個執行器),而且這個函數是當即執行的,因此咱們要給構造函數也傳遞一個executor執行器:
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(executor){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
executor();//默認執行器會當即執行
}
then(){
}
};
module.exports = Promise;//導出
複製代碼
callBacks.js的new Promise實例時,executor執行的時候傳遞了兩個函數,而且屬於當前的Promise實例,不須要再then中拿到,因此咱們能夠在constructor中聲明兩個函數:成功函數和失敗函數。而且將這兩個函數傳遞給executor執行器:
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(executor){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
//成功函數
let resolve = () => {
}
//失敗函數
let reject = () => {
}
executor(resolve,reject);//默認執行器會當即執行
}
then(){
}
};
module.exports = Promise;//導出
複製代碼
成功函數和失敗的函數都接受一個值,value和reason,由於在then也須要用到這兩個值,因此咱們須要定義一個變量。咱們在執行完成功和失敗函數以後,須要更新Promise的狀態:
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(executor){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
this.value = undefined;//成功的值
this.reason = undefined;//失敗的緣由
//成功函數
let resolve = (value) => {
this.value = value;
this.status = RESOLVED;//Promise實例狀態更新爲成功狀態:resolved狀態
}
//失敗函數
let reject = (reason) => {
this.reason = reason;
this.status = REJECTED;//Promise實例狀態更新爲失敗狀態:rejected狀態
}
executor(resolve,reject);//默認執行器會當即執行
}
then(){
}
};
module.exports = Promise;//導出
複製代碼
由於Promise實例的狀態一旦改變了就不能再變了,也就是說咱們調用了resolve函數就不能再調用reject函數了,因此咱們必須添加添加狀態判斷,只要狀態時等待狀態pending時,才執行函數:
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(executor){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
this.value = undefined;//成功的值
this.reason = undefined;//失敗的緣由
//成功函數
let resolve = (value) => {
if(this.status === PENDING){//// 屏蔽調用,調了成功函數以後不能調失敗函數
this.value = value;
this.status = RESOLVED;//調用成功以後,Promise狀態變成resolved狀態
}
}
//失敗函數
let reject = (reason) => {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;////調用失敗以後,Promise狀態變成rejected狀態
}
}
executor(resolve,reject);//默認執行器會當即執行
}
then(){
}
};
module.exports = Promise;//導出
複製代碼
由於執行器執行的時候內部可能會報錯,會拋出異常,這個時候添加try...catch進行邏輯處理,若是內部拋出異常的話至關於調用了reject失敗函數:
接下來開始調用then( )方法,then能夠放置了兩個函數:
那麼何時調用哪個方法呢?這要根據Promise實例的狀態來判斷了:
Promise實例還有訂閱發佈功能:
未完待續,後續更新~~
//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
constructor(executor){//構造函數
this.status = PENDING;//Promise實例的默認pending狀態
this.value = undefined;//成功的值
this.reason = undefined;//失敗的緣由
this.onResolveCallBacks = [];//成功的回調的數組
this.onRejectCallBacks = [];//失敗的回調的數組
//成功函數
let resolve = (value) => {
if(this.status === PENDING){//// 屏蔽調用,調了成功函數以後不能調失敗函數
this.value = value;
this.status = RESOLVED;//調用成功以後,Promise狀態變成resolved狀態
this.onResolveCallBacks.forEach(fn => fn())//發佈
}
}
//失敗函數
let reject = (reason) => {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED;////調用失敗以後,Promise狀態變成rejected狀態
this.onRejectCallBacks.forEach(fn => fn())//發佈
}
}
//執行器執行時,可能內部報錯:
try{
executor(resolve,reject);//默認執行器會當即執行,接受resolve,reject做爲參數
}catch(error){
reject(error);//若是執行器執行時發生錯誤,等價於調用了失敗方法
}
}
then(onfulfilled,onrejected){/// then 目前有兩個參數:onfulfilled,onrejected
//同步狀況:成功以後執行邏輯
if(this.status === RESOLVED){
onfulfilled(this.value);
}
//同步狀況:失敗以後執行邏輯
if(this.status === PENDING){
onrejected(this.reason);
}
// 若是是異步就先訂閱號
if (this.status === PEDING) {
this.onResolveCallBacks.push(() => {
// todo
onfulfilled(this.value)
})
this.onRejectCallBacks.push(() => {
// todo
onrejected(this.value)
})
}
}
};
module.exports = Promise;//導出
複製代碼
console.log('A');
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
})
};
sleep(1000).then(() => {
console.log('B');
});
//先輸出A,延遲1秒以後輸出B
//或
console.log('A');
const sleep = ((time)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
},time)
})
})
sleep(1000).then(()=>{
console.log('B');
})
複製代碼
const sleep = ((time)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
},time)
})
})
async function sleepAsync(){
await sleep(1000);
console.log('B');
}
複製代碼
console.log("A");
const sleep = ((time)=>{
return new Promise((resolive)=>{
setTimeout(()=>{
resolve();
},time)
})
})
function* sleepGenerator(time){
yeild sleep(time);
}
sleepGenerator(1000).next().value.then(()=>{
console.log("B");
})
複製代碼
類似的面試題:
常常有業務需求,要等幾秒才進行下一步操做,也是使用以上的思路
三個亮燈函數已經存在:
思路:
紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次,意思就是3秒,執行一次 red 函數,2秒執行一次 green 函數,1秒執行一次 yellow 函數,不斷交替重複亮燈,意思就是按照這個順序一直執行這3個函數,這步能夠就利用遞歸來實現。
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const sleep = ((time,fn)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
fn();//哪一個燈亮
resolve();
},time)
})
})
let step = (()=>{
Promise.resolve().then(()=>{
return sleep(3000,red);
}).then(()=>{
return sleep(2000,green);
}).then(()=>{
return sleep(1000,yellow);
}).then(()=>{
step();
})
})
複製代碼
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
//輸入1
複製代碼
Promise.resolve 方法的參數若是是一個原始值,或者是一個不具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲resolved,Promise.resolve 方法的參數,會同時傳給回調函數。
then 方法接受的參數是函數,而若是傳遞的並不是是一個函數,它實際上會將其解釋爲 then(null),這就會致使前一個 Promise 的結果會穿透下面。
...
學習寫做中,持續補充更新,記錄很差的地方望指出修改,共同進步~
參考資料: