手寫Promise

前言

Promise對(duì)于前端的重要性自不必多說(shuō),網(wǎng)上文章也很多,那我為什么還要重復(fù)寫這篇呢篡石?因?yàn)槟呐驴偨Y(jié)的不準(zhǔn)確不全面,原理這東西還是得自己總結(jié)調(diào)試西采,細(xì)節(jié)太多了凰萨,本篇只是簡(jiǎn)單介紹下規(guī)范,并不會(huì)全盤照搬械馆,重點(diǎn)還是實(shí)現(xiàn)的準(zhǔn)確性胖眷,供大家參考。

本篇介紹

  1. 介紹術(shù)語(yǔ)和規(guī)范霹崎,這東西看似不重要珊搀,但很容易混淆,影響記憶質(zhì)量
  2. 通過(guò)PromiseA+規(guī)范自己封裝一個(gè)Promise類
  3. Promise API 的使用和原理
  4. Promise常見(jiàn)的問(wèn)題

一尾菇、術(shù)語(yǔ)和規(guī)范

術(shù)語(yǔ)

  1. thenable:如果一個(gè)對(duì)象或函數(shù)有一個(gè)方法名稱是then境析,那么就說(shuō)它是“具有調(diào)用then方法能力的”囚枪,able在英語(yǔ)語(yǔ)境里是具有某能力的意思。
  2. promise:thenable的對(duì)象或函數(shù)劳淆,是Promise的實(shí)例链沼,遵循PromiseA+規(guī)范;規(guī)范可以理解為產(chǎn)品說(shuō)明書沛鸵,promise是產(chǎn)品括勺,Promise類是生產(chǎn)產(chǎn)品的工廠
  3. value:promise成功解決時(shí),傳入resolve回調(diào)函數(shù)中的參數(shù)曲掰,規(guī)范中寫明了各種可能的數(shù)據(jù)類型疾捍,如 undefined、thenable 或一個(gè)新的 promise 等
  4. reason:promise失敗時(shí)蜈缤,傳入reject回調(diào)函數(shù)的參數(shù)拾氓,表明拒絕的原因

規(guī)范

Promise States

Promise 應(yīng)該有三種狀態(tài),通過(guò)調(diào)用 resolve/reject 方法來(lái)改變狀態(tài)底哥,一經(jīng)改變后不可修改咙鞍。

狀態(tài) 描述
pending - 初始默認(rèn)狀態(tài),表示期約正在等待解決或拒絕<br />- 調(diào)用 resolve() 會(huì)將其變?yōu)?fulfilled 狀態(tài)<br />- 調(diào)用 reject() 會(huì)將其變?yōu)?rejected 狀態(tài)
fulfilled 期約解決的狀態(tài)趾徽,為最終態(tài)续滋,后續(xù)操作狀態(tài)均不可改變
rejected 期約拒絕的狀態(tài),為最終態(tài)

狀態(tài)流轉(zhuǎn)過(guò)程:

image-20220111141343383

then

promise 應(yīng)該有一個(gè)then方法孵奶,當(dāng)解決或拒絕時(shí)會(huì)調(diào)用 then 方法來(lái)處理結(jié)果 x疲酌,并返回一個(gè)promise,其狀態(tài)依賴處理結(jié)果 x了袁。

promise.then(onFulfilled, onRejected)
  1. 參數(shù)

    1. onFulfilled 和 onRejected 必須為函數(shù)朗恳,否則會(huì)被忽略
  2. onFulfilled

    1. promise 狀態(tài)變?yōu)?fulfilled 時(shí),要調(diào)用then中的 onFulfilled() 方法载绿,傳入?yún)?shù) value
  3. onRejected

    1. promise 狀態(tài)變?yōu)?rejected 時(shí)粥诫,調(diào)用 onRejected() 方法,傳入?yún)?shù) reason
  4. onFulfilled 和 onRejected 共性

    1. 狀態(tài)為 pending 時(shí)不可調(diào)用崭庸;
    2. 只允許調(diào)用一次怀浆;
    3. 應(yīng)該是個(gè)微任務(wù)(通過(guò) queueMicrotask 包裝傳入的回調(diào)實(shí)現(xiàn));
  5. then() 方法可被多次調(diào)用

    1. then()方法執(zhí)行時(shí)怕享,會(huì)把回調(diào)添加到隊(duì)列中执赡,當(dāng)狀態(tài)從 pending 變?yōu)榻鉀Q/拒絕時(shí),會(huì)依次執(zhí)行這些回調(diào)
  6. then() 的返回值是個(gè) promise

    1. promise2 = promise1.then(onFulfilled, onRejected)
    2. 調(diào)用 then 時(shí)函筋,promise2 就已經(jīng)創(chuàng)建沙合,接下來(lái)有兩種情況改變其狀態(tài):
      1. 當(dāng) onFulfilled 或 onRejected 正常傳入,并執(zhí)行返回結(jié)果 x 后跌帐,調(diào)用一個(gè)方法名為 resolvePromise 的處理函數(shù)首懈,將結(jié)果 x 傳參進(jìn)去芳来,promise2 就會(huì)根據(jù)結(jié)果解決或拒絕;
      2. onFulfilled 或 onRejected 未傳入猜拾,則 promise2 根據(jù) promise1 的 value/reason 觸發(fā)狀態(tài)變更 fulfilled/rejected
  7. resolvePromise

    1. resolvePromise(promise2, x, resolve, reject)

    2. 情況一:promise2 和 x 是同一引用

      傳入 promise2 是為了判斷 x 是否就是 promise2,出現(xiàn)原因是 promise2 是 then 執(zhí)行后立刻返回的佣盒,所以 then 中的回調(diào)函數(shù)是能訪問(wèn)到作用域鏈上端的該變量的挎袜,這種自己的狀態(tài)等待自己狀態(tài)變更才能變更的錯(cuò)誤邏輯,會(huì)直接調(diào)用 reject(reason) 將 promise2 變?yōu)榫芙^肥惭,reason 是 TypeError

    3. 情況二:x 是一個(gè)新的promise

      此時(shí) promise2 取 x 的最終狀態(tài)盯仪,因?yàn)閜romise可能還會(huì)得到promise,而promise2會(huì)在最后一個(gè)非promise處解決或拒絕

    4. 情況三:x 是一個(gè)對(duì)象或函數(shù)

      首先判斷是否有 then 方法蜜葱,沒(méi)有直接拒絕全景,否則將其視為一個(gè)未執(zhí)行 then 的 promise,在 x 環(huán)境中執(zhí)行一下 then牵囤,由于其是用戶自己實(shí)現(xiàn)的 then 方法爸黄,onFulfilled 中對(duì)結(jié)果 y 調(diào)用 resolvePromise,用以解決或拒絕 promise2揭鳞;根據(jù) promise 規(guī)范中的 then 方法對(duì)用戶的 then 方法做判斷并處理異常炕贵。

二、實(shí)現(xiàn) Promise

這里為了看著更符合直覺(jué)野崇,直接用 ES6 的類來(lái)實(shí)現(xiàn)称开,調(diào)用方法形如 new MyPromise(...)

1. 先看著規(guī)范把實(shí)例結(jié)構(gòu)搭出來(lái)

// 定義狀態(tài)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// MyPromise類
class MyPromise {
  // 狀態(tài)變更回調(diào)函數(shù)隊(duì)列
  FULFILLED_CALLBACK_LIST = [];
  REJECTED_CALLBACK_LIST = [];
  constructor(fn) {
    // 實(shí)例屬性1:狀態(tài)
    this.status = PENDING; // 初始化是pending狀態(tài)
    // 實(shí)例屬性2:結(jié)果/原因
    this.value = null;
    this.reason = null;
  }
  resolve(value) {

  }
  reject(reason) {

  }
  then(onFulfilled, onRejected) {

  }
  resolvePromise(promise2, x, resolve, reject) {

  }
}

2. 實(shí)現(xiàn) resolve 和 reject

這兩個(gè) api 調(diào)用時(shí)就是為了改變狀態(tài)乓梨,狀態(tài)變更后的邏輯放到 set status() {} 中實(shí)現(xiàn)鳖轰,這樣做的話 api 的工作更專一。

class MyPromise {
  constructor(fn) {
    // ...
    // 創(chuàng)建實(shí)例時(shí)就會(huì)調(diào)用傳入的 fn 函數(shù)
    try {
      fn(
        this.resolve.bind(this),
        this.reject.bind(this)
      );
    } catch(err) {
      this.reject(err); // 非函數(shù)就拒絕
    }
  }
  resolve(value) {
    if(this.status === PENDING) {
      this.status = FULFILLED; // 變更狀態(tài)
      this.value = value; // 保存值供后續(xù)邏輯使用
    }
  }
  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED;
      this.reason = reason;
    }
  }
}

3. 實(shí)現(xiàn) then 方法

  • 對(duì)回調(diào)進(jìn)行兼容扶镀,透?jìng)?value/reason蕴侣;
  • then 方法返回 promise,根據(jù)狀態(tài)決定如果回調(diào)處理邏輯狈惫;
  • 回調(diào)要求是微任務(wù)睛蛛,所以要對(duì)其封裝一層;
function isFunction(param) {
  return typeof param === 'function';
}
class MyPromise {
  then(onFulfilled, onRejected) {
    /************* 1. 兼容回調(diào) *************/
    // 若未傳入回調(diào)胧谈,則 promise2 的狀態(tài)和value/reason 都與 promise1 一致
    // 所以平時(shí)寫的 catch 方法其實(shí)是 promise2 調(diào)用的忆肾,會(huì)將結(jié)果透?jìng)鬟M(jìn)去
    const realOnFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => {
      return value;
    };
    const realOnRejected = isFunction(onRejected) ? onRejected : reason => {
      throw reason;
    };
    /************* 2. 返回值是promise *************/
    const promise2 = new MyPromise((resolve, reject) => {
      /************* 3. 封裝微任務(wù),并用resolvePromise處理回調(diào)結(jié)果 *************/
      const fulfilledMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnFulfilled(this.value);
            // 根據(jù)then回調(diào)結(jié)果處理promise2
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err); // 若執(zhí)行過(guò)程中報(bào)錯(cuò)菱肖,則直接拒絕
          }
        });
      }
      const rejectedMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        });
      }
      /************* 4. 根據(jù)當(dāng)前實(shí)例狀態(tài)決定調(diào)用then的哪個(gè)回調(diào) *************/
      switch(this.status) {
        case FULFILLED:
          fulfilledMicrotask(); // 若狀態(tài)已為最終態(tài)客冈,則直接執(zhí)行回調(diào)
          break;
        case REJECTED:
          rejectedMicrotask();
          break;
        case PENDING:
          // 若狀態(tài)是pending,則先緩存回調(diào)
          // 在pending狀態(tài)變更之前稳强,then可以被多次調(diào)用场仲,所以要用隊(duì)列來(lái)維護(hù)回調(diào)
          this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
          this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
      }
    });
    /************* 5. 返回promise *************/
    return promise2;
  }
}

4. 狀態(tài)變更邏輯

當(dāng)狀態(tài)改變時(shí)和悦,要清空?qǐng)?zhí)行回調(diào)列表,這里用setter監(jiān)聽(tīng)變更渠缕,所以需要將實(shí)例屬性status進(jìn)行改造:

class MyPromise {
  constructor(fn) {
    // ...
    this._status = PENDING; // 原始變量
  }
  get status() { // getter
    return this._status;
  }
  set status(newStatus) { // setter
    this._status = newStatus;
    switch(newStatus) {
      case FULFILLED:
        this.FULFILLED_CALLBACK_LIST.forEach(callback => {
          callback(this.value);
        });
        break;
      case REJECTED:
        this.REJECTED_CALLBACK_LIST.forEach(callback => {
          callback(this.reason);
        });
        break;
    }
  }
}

5. 實(shí)現(xiàn) resolvePromise

這個(gè)函數(shù)是對(duì) then 中回調(diào)結(jié)果 x 分情況討論鸽素,不同情況會(huì)解決或拒絕 then 返回的 promise2;情況比較多亦鳞,所以需要多加練習(xí)并記憶:

resolvePromise(promise2, x, resolve, reject) {
  /*********** 情況1:是自己 ***********/
  if(promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  /*********** 情況2:是promise ***********/
  if(x instanceof MyPromise) {
    x.then((y) => {
      // 遞歸下去馍忽,直到遇到第一個(gè)非promise,promise2就會(huì)解決/拒絕
      this.resolvePromise(promise2, y, resolve, reject);
    }, reject);
  } else if (typeof x === 'object' && x !== null || isFunction(x)) {
    /*********** 情況3:引用類型燕差,判斷是否為thenable ***********/
    // 獲取結(jié)果上的then方法
    let then = null;
    try {
      then = x.then;
    } catch(err) {
      return reject(err); // 防止用戶寫個(gè)會(huì)拋錯(cuò)的getter
    }
    // 判斷是否為thenable
    if(isFunction(then)) {
      let called = false;
      // 由于是thenable遭笋,就當(dāng) x是其他符合規(guī)范的 Promise的實(shí)例
      // 所以then要在實(shí)例環(huán)境進(jìn)行才能正確拿到this.value等
      try {
        then.call(
          x,
          y => {
            if(called) return; // 方法不能重復(fù)調(diào)用
            called = true;
            this.resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if(called) return;
            called = true;
            reject(r);
          }
        );
      } catch(err) {
        // 防止then中調(diào)用完onFulfilled(value)后拋個(gè)錯(cuò)之類的情況
        if(called) return;
        reject(err);
      }
    } else {
      resolve(x); // 普通引用類型直接解決
    }
  } else {
    /*********** 情況4:基礎(chǔ)類型 ***********/
    resolve(x); // 基本類型直接解決
  }
}

6. 補(bǔ)充上實(shí)例方法 catch

上述5點(diǎn),其實(shí)已經(jīng)能跑如下測(cè)試了:

const test = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('解決');
    }, 1000);
}).then(console.log);

console.log('同步代碼');

setTimeout(() => {
  console.log('宏任務(wù)');
}, 2000);

結(jié)果如圖:

image-20220112102902064

不過(guò)徒探,完整的 promise 實(shí)例還包括 catch瓦呼、finally 方法,catch() 其實(shí)就是 then() 方法僅傳入第二個(gè)錯(cuò)誤處理回調(diào)的包裝函數(shù)测暗,目的是更加重點(diǎn)關(guān)注異步調(diào)用的錯(cuò)誤而非結(jié)果央串;

而 finally() 方法是不管狀態(tài)如何都執(zhí)行回調(diào),需要注意的是偷溺,finally 僅表示完成蹋辅,但狀態(tài)未知,也就不能給用戶提供value 或 reason挫掏,因?yàn)闆](méi)法做區(qū)分侦另,所以不能給回調(diào)帶參數(shù);而且 finally 還有另一個(gè)特性尉共,就是當(dāng)回調(diào)未報(bào)錯(cuò)或者不是一個(gè) rejected 狀態(tài)的 promise 時(shí)褒傅,finally 的返回值要求是個(gè)能透?jìng)髟?promise 結(jié)果的 promise,具體可見(jiàn)代碼注釋:

catch(onRejected) {
  return this.then(null, onRejected);
}
finally(cb) {
  // 無(wú)論狀態(tài)如何袄友,都執(zhí)行回調(diào)殿托,且返回值是個(gè) promise,那么自然想到用 then(cb, cb)
  // 但 finally 有兩個(gè)要求剧蚣,1. 回調(diào)不能帶參數(shù)支竹,2. 透?jìng)髟璸romise結(jié)果
  // 所以不難想到封裝一層函數(shù)
  // 但需要注意的是,若 cb 返回個(gè)promise鸠按,則需等待promise狀態(tài)解決才能改變?yōu)橥競(jìng)鹘Y(jié)果
  // 所以這里用Promise.resolve()包一層兼容 cb是 promise的情況
  return this.then(
    value => {
      // cb()解決會(huì)透?jìng)鲾?shù)據(jù)礼搁,拒絕會(huì)走常規(guī)流程,即暴露 cb自己的 reason
      return MyPromise.resolve(cb()).then(() => value);
    },
    reason => {
      // cb()解決才會(huì)透?jìng)髟?promise的 reason目尖,供后續(xù) catch使用
      // 拒絕會(huì)走常規(guī)流程馒吴,即暴露 cb自己的 reason
      return MyPromise.resolve(cb()).then(() => { throw reason });
    });
}

7. 補(bǔ)充上類靜態(tài)方法 resolve、reject、race饮戳、all

除了實(shí)例用法豪治,Promise 類本身有幾個(gè)常見(jiàn)靜態(tài)方法:

  • Promise.all(list: iterable):all 方法傳入可迭代結(jié)構(gòu)如數(shù)組,每項(xiàng)可以是任意類型或promise扯罐,內(nèi)部會(huì)將所有項(xiàng)轉(zhuǎn)化為期約负拟,返回值是個(gè) promise,當(dāng)所有結(jié)果都正確返回后才會(huì)解決歹河,有任意一個(gè)期約項(xiàng)為 reject 則返回值的 promise 就是拒絕齿椅;若要所有結(jié)果,哪怕是某項(xiàng)狀態(tài)為 rejected启泣,那就用 Promise.allSettled()
  • Promise.race(list: iterable):傳參同 all 方法,返回值也是 promise示辈,區(qū)別是當(dāng)某項(xiàng)期約解決或拒絕后寥茫,結(jié)果就直接解決或拒絕,其結(jié)果就是這個(gè)最先完成的期約value或reason
  • Promise.resolve(promise | thenable | any):返回一個(gè)promise矾麻,狀態(tài)視傳入值而定纱耻,若傳入的是 promise則冪等返回原promise,若為thenable险耀,則執(zhí)行 then 方法弄喘,promise 狀態(tài)跟隨 then 的結(jié)果;若是其他類型值甩牺,則返回的 promise 的狀態(tài)直接為 fulfilled蘑志,value值就是傳入的數(shù)據(jù)
  • Promise.reject(promise | thenable | any):返回一個(gè)狀態(tài)為 rejected 的 promise,reason值就是傳入的參數(shù)

還有 Promise.allSettled()贬派,Promise.any() 這兩個(gè)方法和 Promise.all() 類似急但,且面試題也會(huì)出一些變種,比如任務(wù)有優(yōu)先級(jí)的概念等搞乏,這個(gè)等之后總結(jié)面試題專題時(shí)再寫波桩,因?yàn)閱?wèn)原理時(shí)一般只會(huì)問(wèn)到 then() 方法,所以這里先簡(jiǎn)單實(shí)現(xiàn) Promise.all() 和 Promise.race()请敦,另外2個(gè) api 以及變種面試題之后再討論镐躲。

/************* Promise.resolve(value) *************/
static resolve(value) {
  // 若已經(jīng)是promise,則冪等返回
  if (value instanceof MyPromise) {
    return value;
  }
  // 否則返回一個(gè)promise侍筛,狀態(tài)依賴value
  return new MyPromise((resolve) => {
    resolve(value);
  });
}
/************* Promise.reject(reason) *************/
static reject(reason) {
  // 返回一個(gè)拒絕的promise萤皂,注意是個(gè)新的 promise
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
}
  /************* Promise.race(list) *************/
  static race(anyList) {
    return new MyPromise((resolve, reject) => {
      const len = anyList.length;
      if(len === 0) {
        resolve(); // 無(wú)數(shù)據(jù)時(shí)直接返回一個(gè)空promise
      } else {
        for(let i = 0; i < len; i++) {
          MyPromise.resolve(anyList).then(
            value => {
              resolve(value); // 只要有某項(xiàng)解決就將結(jié)果解決
            },
            reason => {
              reject(reason); // 只要有某項(xiàng)拒絕就將結(jié)果拒絕
            }
          );
        }
      }
    })
  }
  /************* Promise.all(list) *************/
  static all(anyList) { // 1. all是靜態(tài)方法
    // 2. 返回值是promise
    return new MyPromise((resolve, reject) => {
      // 3. 參數(shù)類型判斷,需要傳入可迭代結(jié)構(gòu)
      if(!anyList || typeof anyList[Symbol.iterator] !== 'function') {
        return reject(new TypeError('arguments must be iterable'));
      }
      const len = anyList.length;
      const res = [];
      let counter = 0;

      for(let i = 0; i < len; i++) {
        // 4. 參數(shù)類型期約化
        MyPromise.resolve(anyList[i]).then(value => {
          counter++;
          // 5. 不能用push勾笆,因?yàn)榻Y(jié)果順序與參數(shù)一一對(duì)應(yīng)
          res[i] = value;
          // 等待所有結(jié)果成功返回后解決期約
          if(counter === len) {
            resolve(res);
          }
        }).catch(reason => {
          reject(reason);
        });
      }
    }); 
  }

跑一段測(cè)試代碼:

const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 2000);
})
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(3);
  }, 3000);
})
Promise.all([p2, p1, p3]).then(res => {
  console.log('all_成功 ', res);
}).catch(e => {
  console.log('all_失敗 ', e);
});
Promise.race([p2, p1, p3]).then(res => {
  console.log('race_成功 ', res);
}).catch(e => {
  console.log('race_失敗 ', e);
});
image-20220112145417707

拒絕期約的測(cè)試代碼可以自己改動(dòng)敌蚜,不再贅述。

三窝爪、總結(jié)

至此已初步根據(jù)規(guī)范實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Promise弛车,細(xì)節(jié)并沒(méi)有考究很細(xì)齐媒,比如參數(shù)類型的校驗(yàn),兼容性的考究纷跛,以及全部靜態(tài)方法的實(shí)現(xiàn)等等喻括;因?yàn)槲蚁雮鬟_(dá)的是,Promise原理為何每篇文章實(shí)現(xiàn)都不一樣贫奠,為啥一定要有 then 方法唬血,或?yàn)樯队心敲炊?try-catch,這一切讓人難以理解或記憶的原因唤崭,就是有一個(gè)東西叫PromiseA+規(guī)范拷恨,規(guī)范就像試卷上的題目,要求是啥樣谢肾,就得實(shí)現(xiàn)成啥樣腕侄;理解了這個(gè)大前提,代碼實(shí)現(xiàn)方式是否嚴(yán)謹(jǐn)優(yōu)雅芦疏,就完全看你自己和面試官要求了冕杠。剩下的 allSettled() 和 any() 方法,以及并發(fā)請(qǐng)求的變種面試題酸茴,會(huì)在之后總結(jié)分预,因?yàn)榇笾滤悸范枷嗨疲?Promise 原理考察也不太會(huì)關(guān)心這幾個(gè)類似的api薪捍,因此將這一類整理到一起再總結(jié)笼痹。

我自己用 node 17.3.1 版本跑通了所有測(cè)試,可能實(shí)現(xiàn)的地方都疏漏之處酪穿,望大家?guī)兔χ刚氤粍俑屑ぁV翱催^(guò)很多文章昆稿,發(fā)現(xiàn)我不理解的地方纺座,別人都會(huì)一嘴帶過(guò),有的博客甚至就是復(fù)制粘貼溉潭,沒(méi)經(jīng)過(guò)自己的思考净响,可想而知我看到這些文章時(shí)腦袋是有多大。話雖如此喳瓣,我自己總結(jié)的這篇文章也會(huì)有讓人不理解的地方馋贤,不過(guò)準(zhǔn)確性還是能保證的,有不理解的地方可以給我評(píng)論留言畏陕,我會(huì)一一解答的配乓,源碼放到了下面的參考鏈接中。

四、參考

完整代碼

MDN犹芹,關(guān)于Promise API 的準(zhǔn)確描述

Promise/A+ 規(guī)范

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崎页,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子腰埂,更是在濱河造成了極大的恐慌飒焦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屿笼,死亡現(xiàn)場(chǎng)離奇詭異牺荠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)驴一,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門休雌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肝断,你說(shuō)我怎么就攤上這事挑辆。” “怎么了孝情?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洒嗤。 經(jīng)常有香客問(wèn)我箫荡,道長(zhǎng),這世上最難降的妖魔是什么渔隶? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任羔挡,我火速辦了婚禮,結(jié)果婚禮上间唉,老公的妹妹穿的比我還像新娘绞灼。我一直安慰自己,他們只是感情好呈野,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布低矮。 她就那樣靜靜地躺著,像睡著了一般被冒。 火紅的嫁衣襯著肌膚如雪军掂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天昨悼,我揣著相機(jī)與錄音蝗锥,去河邊找鬼。 笑死率触,一個(gè)胖子當(dāng)著我的面吹牛终议,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼穴张,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼细燎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起陆馁,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤找颓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叮贩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體击狮,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年益老,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彪蓬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捺萌,死狀恐怖档冬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情桃纯,我是刑警寧澤酷誓,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站态坦,受9級(jí)特大地震影響盐数,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伞梯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一玫氢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谜诫,春花似錦漾峡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至且预,卻和暖如春牺陶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辣之。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工掰伸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怀估。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓狮鸭,卻偏偏與公主長(zhǎng)得像合搅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歧蕉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 原文詳見(jiàn):Promise實(shí)現(xiàn)原理(附源碼)參考文章:BAT前端經(jīng)典面試問(wèn)題:史上最最最詳細(xì)的手寫Promise教程...
    張小明_to閱讀 99評(píng)論 0 1
  • 從面試角度出發(fā)惯退,可能我們會(huì)經(jīng)常面臨這幾個(gè)問(wèn)題: Promise解決了什么問(wèn)題赌髓? Promise的業(yè)界實(shí)現(xiàn)都有哪些?...
    Amillly閱讀 442評(píng)論 0 0
  • 手寫promise 帶大家手寫一個(gè) promis催跪。在手寫之前我會(huì)先簡(jiǎn)單介紹一下為什么要使用promise锁蠕、prom...
    大俠叫誰(shuí)閱讀 652評(píng)論 0 6
  • 1. promise要解決的問(wèn)題: 腦筋急轉(zhuǎn)彎:把牛關(guān)進(jìn)冰箱里,要分幾步懊蒸? 很顯然荣倾,這三個(gè)操作不能顛倒順序,否則任...
    月上秦少閱讀 1,567評(píng)論 0 3
  • Promise的聲明 首先骑丸,promise肯定是一個(gè)類舌仍,我們就用class來(lái)聲明。 由于new Promise((...
    oWSQo閱讀 230評(píng)論 0 1