完全理解 Promise,從最完整的實現(xiàn)開始

從標(biāo)準(zhǔn)實現(xiàn)入手理解 Promise

為什么寫這篇

網(wǎng)上解釋已經(jīng)一抓一大把,但是個人覺得大部分文章可以實現(xiàn) Promise/A+,但是對真實細(xì)節(jié)沒完全實現(xiàn):

  • 真正的 microtask(大部分用的 setTimeout 代替)
  • then 傳入回調(diào)函數(shù)返回 Promise 對象的情況
  • 構(gòu)造函數(shù) executor 的細(xì)節(jié),以及 executor 的 resolve 參數(shù)傳入 Promise 對象的情況
  • Promise.resolve 傳入 Promise 對象的情況
  • Promise 各種靜態(tài)函數(shù)傳入的是可迭代對象而非數(shù)組
  • 介紹清楚循環(huán)調(diào)用阻止的情況和多次 resolve 阻止的情況

本人用自己淺薄的知識嘗試去覆蓋最真實的 Promise 所有實現(xiàn)情況,并且用純 js 實現(xiàn),從中理解到真正的 Promise 是怎么樣的。

什么是 Promise

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象。

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理,并非只能解決讀取接口這種異步操作。

在此之前如何解決異步操作

在 Promise 出現(xiàn)之前其實也有出現(xiàn)了類 Promise 的庫,例如QJQueryDeferred 等。但是通用的解決方案就是回調(diào)函數(shù)。

回調(diào)函數(shù)本身是沒問題的,但是如果需要一個接一個調(diào)用(嵌套)時,很容易掉入回調(diào)地獄

ajax({
  url: './index',
  success: function (value) {
    ajax({
      url: value.url,
      success: function (value2) {
        ajax({
          url: value2.url,
          success: function (value3) {
            ajax({
              url: value3.url,
              success: function (value4) {
                // .. do something else
              },
            });
          },
        });
      },
    });
  },
});

在有 Promise 后就可以寫成:

const request = url =>
  new Promise((resolve, reject) => {
    ajax({
      url,
      success: resolve,
      fail: reject,
    });
  });

request('.index')
  .then(({ url }) => request(url))
  .then(({ url }) => request(url))
  .then(({ url }) => request(url))
  .then(value4 => {
    // .. do something else
  });

甚至在配合 es2017 中的 async 函數(shù)可以寫成:

(async () => {
  const { url } = await request('.index');
  const { url: url2 } = await request(url);
  const { url: url3 } = await request(url2);
  const value4 = await request(url3);
  // .. do something else
})();

整個代碼就清晰易懂而且優(yōu)雅簡潔。

Promise 特點

Promise對象有以下兩個特點。

(1)對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):pending(進行中)、fulfilled(已成功)和 rejected(已失?。?。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果Promise對象的狀態(tài)改變,只有兩種可能:從 pending 變?yōu)?fulfilled 和從 pending 變?yōu)?rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結(jié)果,這時就稱為 resolved(已定型)。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。

Promise 的 api 和用法

這里就不詳細(xì)列舉了,詳細(xì)可以參考 es6 入門中的Promise 章
下面會默認(rèn)讀者已清楚了解 Promise 的 api。

在開始前...

是否能說清楚下面打印的是什么,而且說出思路么?

new Promise(res => {
  res();
  console.log(1);
  // 代碼塊1
})
  .then(() => {
    // 代碼塊2
    console.log(2);
    new Promise(res => {
      res();
      console.log(3);
    })
      .then(() => {
        // 代碼塊3
        console.log(4);
        new Promise(res => {
          res();
          console.log(5);
        }).then(() => {
          // 代碼塊4
          console.log(8);
        });
      })
      .then(() => {
        // 代碼塊5
        console.log(9);
        new Promise(res => {
          res();
          console.log(10);
        }).then(() => {
          // 代碼塊6
          console.log(12);
        });
      });
    Promise.resolve()
      .then(() => {
        // 代碼塊7
        console.log(6);
      })
      .then(() => {
        // 代碼塊8
        console.log(11);
      });
  })
  .then(() => {
    // 代碼塊9
    console.log(7);
  });
Promise.reject(
  new Promise((res, rej) => {
    setTimeout(() => {
      rej(133222);
      res(444);
    }, 2000);
  })
)
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs))
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));
Promise.resolve(
  new Promise((res, rej) => {
    setTimeout(() => {
      rej(133222);
      res(444);
    }, 2000);
  })
)
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs))
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));
new Promise((res, rej) => {
  res(Promise.reject(123));
})
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));
new Promise((res, rej) => {
  rej(Promise.resolve(123));
})
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));
new Promise(res => res(Promise.resolve())).then(() => console.log(2));

Promise.resolve(Promise.resolve()).then(() => console.log(1));
Promise.resolve()
  .then(() => {
    console.log(1);
    return Promise.resolve(5);
  })
  .then(r => {
    console.log(r);
  });

Promise.resolve()
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(4);
  })
  .then(() => {
    console.log(6);
  });

可以自己嘗試思考再去控制臺嘗試,如果回答不上就應(yīng)該往下看啦

Promise 規(guī)范

關(guān)于 Promise 的規(guī)范最早是由 commonjs 社區(qū)提出,畢竟多人接收的就是Promise/A,后面因規(guī)范較為簡單所以在這基礎(chǔ)上提出了Promise/A+,這也是業(yè)界和 ES6 使用的標(biāo)準(zhǔn),而 ES6 在這標(biāo)準(zhǔn)上還新增了 Promise.resolve、Promise.reject、Promise.all、Promise.race、Promise.prototype.catch、Promise.allSettled、Promise.prototype.finally 等方法。

而測試是否符合 Promise/A+標(biāo)準(zhǔn)的可以使用promises-aplus-tests庫來測試,使用方法為在自己實現(xiàn)的 MyPromise 文件中加入如下代碼導(dǎo)出

// MyPromise.js
MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

module.exports = MyPromise;

然后可以安裝并在文件目錄內(nèi)運行

npm install -g promises-aplus-tests

promises-aplus-tests MyPromise.js

微任務(wù)創(chuàng)建器

我們都知道 Promise 的 then 創(chuàng)建的是異步任務(wù)(microtask),而我們需要實現(xiàn) Promise 的話當(dāng)然不能用他來創(chuàng)建,網(wǎng)上各種實現(xiàn)可能基本都是 setTimeout,這個是將任務(wù)推入下一宏任務(wù)(macrotask)中。所以我們需要一個微任務(wù)創(chuàng)建器,這時候就需要用到幾個 api 了:一個是queueMicrotask,還有MutationObserver,這兩個都是可以將回調(diào)函數(shù)推入微任務(wù)隊列的,然后我們可以先封裝一個降級方案:

// 判斷是否函數(shù)
const isFunction = target => typeof target === 'function';

// 判斷是否原生方法
const isNativeFunction = Ctor =>
  isFunction(Ctor) && /native code/.test(Ctor.toString());

// 推入微任務(wù)隊列
const nextTaskQueue = cb => {
  if (
    typeof queueMicrotask !== 'undefined' &&
    isNativeFunction(queueMicrotask)
  ) {
    queueMicrotask(cb);
  } else if (
    typeof MutationObserver !== 'undefined' &&
    (isNativeFunction(MutationObserver) ||
      MutationObserver.toString() === '[object MutationObserverConstructor]')
  ) {
    const observer = new MutationObserver(cb);
    const node = document.createTextNode('1');
    observer.observe(node, {
      characterData: true,
    });
    node.data = '2';
  } else if (typeof process !== 'undefined' && isFunction(process.nextTick)) {
    process.nextTick(cb);
  } else {
    setTimeout(() => {
      cb();
    }, 0);
  }
};

如此,后面我們需要創(chuàng)建異步 tick 就可以使用這個nextTaskQueue。

Promise 結(jié)構(gòu)

可以從日常使用 Promise 中了解到,Promise 需要 new 出實例,并且傳入回調(diào)函數(shù),而回調(diào)函數(shù)接收兩個參數(shù)(resolve、reject),回調(diào)函數(shù)會立刻執(zhí)行。返回的 Promise 實例中可調(diào)用 then 或者 catch 接收完成和錯誤。

Promise 擁有三種狀態(tài)分別為pending等待中、fulfilled已完成、rejected已拒絕,并且初始為等待中,且如果更改了狀態(tài)則無法再更改。

Promise.prototype.then可以接收兩個參數(shù),分別是onFulfilledonRejected回調(diào)函數(shù),Promise.prototype.catch只能接收onRejected。

const STATUS = {
  PENDING: 'PENDING', // 等待中
  FULFILLED: 'FULFILLED', // 已完成
  REJECTED: 'REJECTED', // 已拒絕
};

class MyPromise {
  status = STATUS.PENDING; // 初始化狀態(tài)為等待中

  constructor(executor) {
    const resolve = value => {};
    const reject = reason => {};
    try {
      executor(resolve, reject); // 實例化即刻執(zhí)行
    } catch (err) {
      reject(err); // 發(fā)生錯誤則被捕捉
    }
  }

  then(onFulfiled, onRejected) {}
}

我們發(fā)現(xiàn) Promise 對象在每次使用 then 或者 catch 后獲取的值都會一致不變,而且在完成前多個 then 或者 catch 監(jiān)聽會在完成、拒絕后一個個調(diào)用,所以知道這里會保存值和錯誤以及維護一個完成和拒絕的隊列

const STATUS = {
  PENDING: 'PENDING', // 等待中
  FULFILLED: 'FULFILLED', // 已完成
  REJECTED: 'REJECTED', // 已拒絕
};

class MyPromise {
  _resolveQueue = []; // 完成回調(diào)隊列
  _rejectQueue = []; // 拒絕回調(diào)隊列
  result = void 0; // 完成的值
  state = STATUS.PENDING; // 初始化狀態(tài)為等待中

  constructor(executor) {
    const resolve = value => {
      // 只有在等待中的狀態(tài)才可以resolve
      if (this.state === STATUS.PENDING) {
        try {
          // 如果傳入resolve內(nèi)的為thenable對象,則以它的狀態(tài)為準(zhǔn)
          resolvePromise(this, value, realResolve, reject);
        } catch (err) {
          reject(err);
        }
      }
    };

    const realResolve = value => {
      // 只有在等待中的狀態(tài)才可以resolve
      this.state = STATUS.FULFILLED; // 修改狀態(tài)
      this.result = value; // 保存值
      // 真正的創(chuàng)建了微任務(wù)的封裝
      nextTaskQueue(() => {
        while (this._resolveQueue.length) {
          const callback = this._resolveQueue.shift();
          callback(value); // 一個個執(zhí)行
        }
      });
    };

    const reject = reason => {
      // 與resolve一致,只是修改的狀態(tài)和保存的理由以及執(zhí)行的隊列不一樣
      if (this.state === STATUS.PENDING) {
        this.state = STATUS.REJECTED;
        this.result = reason;
        nextTaskQueue(() => {
          while (this._rejectQueue.length) {
            const callback = this._rejectQueue.shift(); // 獲取拒絕回調(diào)隊列
            callback(reason);
          }
        });
      }
    };

    try {
      executor(resolve, reject); // 實例化即刻執(zhí)行
    } catch (err) {
      reject(err); // 發(fā)生錯誤則被捕捉
    }
  }

  then(onFulfiled, onRejected) {}
}

到這里,構(gòu)造函數(shù)已經(jīng)差不多了,剩下的開始實現(xiàn)then方法。

我們知道,then 后面可以鏈?zhǔn)秸{(diào)用 then,并且then 獲取的值為上一個 then 返回的新 Promise 對象中的值,很多人誤認(rèn)為鏈?zhǔn)秸{(diào)用獲取的是鏈?zhǔn)降念^的 Promise,其實不然,Promise 每個 then 都會創(chuàng)建一個新 Promise,所以你下一個 then 跟最前面的 Promise 不一定有關(guān)系。

而且,如果 then 中傳入的不是函數(shù),則會直接傳出,直到被傳入函數(shù)的 then 捕捉。

然后,在調(diào)用 then 時,Promise 對象可能為三種狀態(tài),但是即使是已完成或已拒絕,也不會立刻執(zhí)行,而是被推入微任務(wù)隊列中。

class MyPromise {
  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value; // 如果不是函數(shù)則傳遞給下一個
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason;
          };
    return new MyPromise((resolve, reject) => {
      if (this.state === STATUS.FULFILLED) {
        nextTaskQueue(() => {
          // 即使Promise對象是已完成,也不會立刻執(zhí)行
          const result = onFulfilled(this.result); // 傳入的回調(diào)可以獲取值
          resolve(result);
        });
      } else if (this.state === STATUS.REJECTED) {
        nextTaskQueue(() => {
          // 即使Promise對象是已拒絕,也不會立刻執(zhí)行
          const result = onRejected(this.result); // 傳入的回調(diào)可以拒絕理由
          reject(result);
        });
      } else if (this.state === STATUS.PENDING) {
        // 如果是等待中,則分別推入回調(diào)隊列中
        this._resolveQueue.push(() => {
          const result = onFulfilled(this.result);
          resolve(result);
        });
        this._rejectQueue.push(() => {
          const result = onRejected(this.result);
          reject(result);
        });
      }
    });
  }
}

到這里為止,基本差不多了,然而并沒有這么簡單?;卣{(diào)函數(shù)可能是任何值,包括返回了一個 Promise 對象,這種情況需要以返回的 Promise 為準(zhǔn)。并且如果是這種情況,后面 then 會被延遲兩個 tick 執(zhí)行,具體實現(xiàn)可以參考 V8 引擎對ResolvePromisePromiseResolveThenableJob的實現(xiàn),這里不作深入講解。

所以這里可以封裝出一個方法專門處理 Promise 以及其回調(diào),以適配所有標(biāo)準(zhǔn)

const resolvePromise = (newPromise, result, resolve, reject) => {
  /**
   * 規(guī)范2.3.1,避免循環(huán)引用
   * e.g. const p = MyPromise.resolve().then(() => p);
   */
  if (newPromise === result) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  /**
   * 用來判斷resolvePormise是否已經(jīng)執(zhí)行過了,如果執(zhí)行過resolve或者reject就不要再往下走resolve或者reject
   * 在一些返回thenable對象中,連續(xù)調(diào)用多次回調(diào)的情況
   * e.g. then(() => {
   *        return {
   *          then(resolve){
   *            resolve(1);
   *            resolve(2);
   *          }
   *        }
   *      })
   * 網(wǎng)上大部分的都沒說這個情況到底是什么
   */
  let called = false;
  if (result !== null && (typeof result === 'object' || isFunction(result))) {
    try {
      const { then } = result;
      if (isFunction(then)) {
        // 規(guī)范2.3.3.3 如果result是個thenable對象,則調(diào)用其then方法,當(dāng)他是Promise
        then.call(
          result,
          value => {
            if (!called) {
              called = true;
              // 現(xiàn)代瀏覽器中,如果then返回是thenable對象則會延遲一次執(zhí)行,而本身的then又會延遲,所以其實是兩次
              nextTaskQueue(() => {
                resolvePromise(newPromise, value, resolve, reject); // 這里需要遞歸取值,直到不是Promise為止
              });
            }
          },
          reason => {
            if (!called) {
              called = true;
              nextTaskQueue(() => {
                reject(reason);
              });
            }
          }
        );
      } else {
        // 規(guī)范2.3.3.4 如果 result不是thenable對象,則返回fulfilled
        resolve(result);
      }
    } catch (err) {
      if (!called) {
        called = true;
        reject(err);
      }
    }
  } else {
    resolve(result);
  }
};

所以 then 方法改為

then(onFulfilled, onRejected) {
  onFulfilled =
    typeof onFulfilled === 'function' ? onFulfilled : value => value; // 如果不是函數(shù)則傳遞給下一個
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : reason => {
          throw reason;
        };
  let newPromise;
  return (newPromise = new MyPromise((resolve, reject) => {
    if (this.state === STATUS.FULFILLED) {
      nextTaskQueue(() => {
        // 即使Promise對象是已完成,也不會立刻執(zhí)行
        try {
          const result = onFulfilled(this.result); // 傳入的回調(diào)可以獲取值
          resolvePromise(newPromise, result, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    } else if (this.state === STATUS.REJECTED) {
      nextTaskQueue(() => {
        // 即使Promise對象是已拒絕,也不會立刻執(zhí)行
        try {
          const result = onRejected(this.result); // 傳入的回調(diào)可以拒絕理由
          resolvePromise(newPromise, result, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    } else if (this.state === STATUS.PENDING) {
      // 如果是等待中,則分別推入回調(diào)隊列中
      this._resolveQueue.push(() => {
        try {
          const result = onFulfilled(this.result);
          resolvePromise(newPromise, result, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
      this._rejectQueue.push(() => {
        try {
          const result = onRejected(this.result);
          resolvePromise(newPromise, result, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    }
  }));
}

最后加入上面說的測試導(dǎo)出,跑一次測試即可
[圖片上傳失敗...(image-67c4ae-1621751597335)]
827 個測試項全通過~!


剩下的可以增加一些 api 實現(xiàn)和判斷即可。

但是要注意的是,靜態(tài)方法包括 all、race、allSettled、any 等,傳入的是任意可迭代對象,包括字符串等,如果傳入的迭代對象中的子元素如果非 Promise 對象,則直接返回,而即使是非 Promise 對象,也是需要推入微任務(wù)在下一 tick 執(zhí)行(很多實現(xiàn)忽略了這些)。

tips

因為個人在網(wǎng)上看了很多類似的,但是并沒有很完整的解釋細(xì)節(jié),例如 called 是做什么的。
所以自己總結(jié)了一下。

因為發(fā)現(xiàn)很多同學(xué)覺得 Promise 就是用來封裝讀接口的通訊方法的,這里表示 Promise不僅僅可以做讀接口封裝,還可以做很多有趣的封裝

例如:

  • wait 等待幾秒后執(zhí)行
const wait = time =>
  new Promise(resolve => {
    const timer = setTimeout(() => resolve(timer), time);
  });

(async () => {
  console.log(1);
  await wait(2000);
  console.log(2);
})();
// print 1
// wait for 2 seconds
// print 2
  • 早期的小程序 api promise 化,因為本人 16 年開始接觸小程序,那時候小程序全是 success 和 fail 回調(diào),用起來很頭疼(現(xiàn)在全支持 thenable 調(diào)用了),所以做了個 promisify 函數(shù)。
const promisify =
  wxapi =>
  (options, ...args) =>
    new Promise((resolve, reject) =>
      wxapi.apply(null, [
        {
          ...options,
          success: resolve,
          fail: err => {
            console.log(err);
            reject(err);
          },
        },
        ...args,
      ])
    );

(async () => {
  await promisify(wx.login)();
  await promisify(wx.checkSession)();
  // session有效!
})();

const loading = (title = '加載中..') => {
  promisify(wx.showLoading)({
    title: i18n.getLocaleByName(title),
    mask: true,
  });
};

最后

在了解了源碼后,其實可以延伸出一些 Promise 執(zhí)行順序的問題

new Promise(res => {
  res();
  console.log(1);
  // 代碼塊1
})
  .then(() => {
    // 代碼塊2
    console.log(2);
    new Promise(res => {
      res();
      console.log(3);
    })
      .then(() => {
        // 代碼塊3
        console.log(4);
        new Promise(res => {
          res();
          console.log(5);
        }).then(() => {
          // 代碼塊4
          console.log(8);
        });
      })
      .then(() => {
        // 代碼塊5
        console.log(9);
        new Promise(res => {
          res();
          console.log(10);
        }).then(() => {
          // 代碼塊6
          console.log(12);
        });
      });
    Promise.resolve()
      .then(() => {
        // 代碼塊7
        console.log(6);
      })
      .then(() => {
        // 代碼塊8
        console.log(11);
      });
  })
  .then(() => {
    // 代碼塊9
    console.log(7);
  });

以上可以解釋一下執(zhí)行順序

  • tick1、代碼塊 1先執(zhí)行,resolve 了,將代碼塊 2推入 nextTick,打印1
  • tick2、代碼塊 2執(zhí)行,打印2,創(chuàng)建 Promise,resolve 了所以將代碼塊 3推入 nextTick,打印3;往下走,Promise.resolve 創(chuàng)建了一個 fulfilled 的 Promise,所以代碼塊 7推入 nextTick,執(zhí)行完畢代碼塊 2,所以代碼塊 9被推入 nextTick
  • tick3、代碼塊 3執(zhí)行,打印4,創(chuàng)建 Promise,resolve 了將代碼塊 4推入 nextTick,打印5,執(zhí)行完 then 所以將下一個 then 的代碼塊 5推入 nextTick;然后代碼塊 7執(zhí)行,打印6,執(zhí)行完所以將下一個 then 的代碼塊 8推入 nextTick;執(zhí)行代碼塊 9,打印7
  • tick4、代碼塊 4執(zhí)行,打印8;代碼塊 5執(zhí)行,打印9,創(chuàng)建新 Promise,resolve 了所以將代碼塊 6推入 nextTick,打印10代碼塊 8執(zhí)行,打印11
  • tick5、代碼塊 6執(zhí)行,打印12

Promise.reject(
  new Promise((res, rej) => {
    setTimeout(() => {
      rej(133222);
      res(444);
    }, 2000);
  })
)
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs))
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));

這里很容易理解,會立刻執(zhí)行第二個 catch 和第三個 then,打印 pending 中的 Promise 對象和 undefined,因為 Promise.reject 會創(chuàng)建一個已 rejected 的 Promise 對象,value 為傳入的值。

Promise.resolve(
  new Promise((res, rej) => {
    setTimeout(() => {
      rej(133222);
      res(444);
    }, 2000);
  })
)
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs))
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));

但是這里就不一樣了,會等待 2 秒后,但是還是第二個 catch 和第三個 then。因為 Promise.resolve 如果傳入的是 thenable 對象,則返回以此為準(zhǔn)。


new Promise((res, rej) => {
  res(Promise.reject(123));
})
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));

這里會以為調(diào)用 then,因為調(diào)用了內(nèi)部的 resolve 方法,其實不然,這里會走 catch 回調(diào)并且打印 catch 和 123,因為 resolve 內(nèi)如果傳入 thenable 對象則會一次為準(zhǔn)

new Promise((res, rej) => {
  rej(Promise.resolve(123));
})
  .then(rs => console.log('then', rs))
  .catch(rs => console.log('catch', rs));

而 reject 則不會。


new Promise(res => res(Promise.resolve())).then(() => console.log(2));

Promise.resolve(Promise.resolve()).then(() => console.log(1));
Promise.resolve()
  .then(() => {
    console.log(1);
    return Promise.resolve(5);
  })
  .then(r => {
    console.log(r);
  });

Promise.resolve()
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(4);
  })
  .then(() => {
    console.log(6);
  });
Promise.resolve()
  .then(() => {
    console.log(1);
    return {
      then(r) {
        r(5);
      },
    };
  })
  .then(r => {
    console.log(r);
  });

Promise.resolve()
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(4);
  })
  .then(() => {
    console.log(6);
  });

這兩個可以一塊說,大部分網(wǎng)上的例子都沒實現(xiàn)這塊的邏輯,也是 Promise 的一個需要注意的細(xì)節(jié):就是 resolve、then 傳入的回調(diào)函數(shù)的返回,如果是 Promise 對象,則會延遲兩個 tick。

為什么呢,這塊當(dāng)然涉及到 v8 實現(xiàn)的源碼,不說這么復(fù)雜簡單化來說的話就是,Promise 會先把 then 執(zhí)行一次,這里會有一個 tick(如果是 thenable 對象則不會,因為并非原生的then),執(zhí)行這個 then 時傳入的回調(diào)會包含另一個 tick 的延遲。

完整實現(xiàn)

下面貼出全代碼實現(xiàn),包含了

  • Promise/A+規(guī)范實現(xiàn)
  • 所有目前(ES2021)的 Promise 實例方法(finally)、靜態(tài)方法(any、allSettled)等的實現(xiàn)
  • 將異步任務(wù)正確推入微任務(wù)隊列
  • then 傳入的回調(diào)函數(shù)返回 Promise 對象,延遲兩個 tick
  • 構(gòu)造函數(shù)的參數(shù) executor 中 resolve 入?yún)魅霝?Promise 對象也會延遲兩個 tick
  • Promise.resolve 傳入 Promise 對象時會直接將其返回出去,Promise.reject 則不然
  • Promise 各種靜態(tài)方法(all、race、any、allsettled)傳入的是可迭代對象而非數(shù)組
  • Promise 各種靜態(tài)方法(all、race、any、allsettled)傳入的可迭代對象成員如果不是 Promise 對象會直接返回,但是也是會進入下一微任務(wù)(很多實現(xiàn)都是直接 resolve 并沒有延遲)

完整代碼在這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容