Axios源碼剖析(轉(zhuǎn))

axios 是一個(gè)基于 Promise 的http請(qǐng)求庫(kù)吨铸,可以用在瀏覽器和node.js中

備注:

  1. 每一小節(jié)都會(huì)從兩個(gè)方面介紹:如何使用 -> 源碼分析
  2. [工具方法簡(jiǎn)單介紹]一節(jié)可先跳過(guò)油够,后面用到了再過(guò)來(lái)查看
  3. axios最核心的技術(shù)點(diǎn)是 如何攔截請(qǐng)求響應(yīng)并修改請(qǐng)求參數(shù)修改響應(yīng)數(shù)據(jù) 和 axios是如何用promise搭起基于xhr的異步橋梁的

目錄結(jié)構(gòu)

├── /dist/                     # 項(xiàng)目輸出目錄
├── /lib/                      # 項(xiàng)目源碼目錄
│ ├── /cancel/                 # 定義取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主類
│ │ ├── dispatchRequest.js     # 用來(lái)調(diào)用http請(qǐng)求適配器方法發(fā)送請(qǐng)求
│ │ ├── InterceptorManager.js  # 攔截器構(gòu)造函數(shù)
│ │ └── settle.js              # 根據(jù)http響應(yīng)狀態(tài)刹枉,改變Promise的狀態(tài)
│ ├── /helpers/                # 一些輔助方法
│ ├── /adapters/               # 定義請(qǐng)求的適配器 xhr、http
│ │ ├── http.js                # 實(shí)現(xiàn)http適配器
│ │ └── xhr.js                 # 實(shí)現(xiàn)xhr適配器
│ ├── axios.js                 # 對(duì)外暴露接口
│ ├── defaults.js              # 默認(rèn)配置 
│ └── utils.js                 # 公用工具
├── package.json               # 項(xiàng)目信息
├── index.d.ts                 # 配置TypeScript的聲明文件
└── index.js                   # 入口文件

注:因?yàn)槲覀冃枰吹拇a都是/lib/目錄下的文件慷暂,所以以下所有涉及到文件路徑的地方荧飞, 我們都會(huì)在/lib/下進(jìn)行查找

名詞解釋

  • 攔截器 interceptors

    (如果你熟悉中間件烙无,那么就很好理解了毫捣,因?yàn)樗鸬降木褪腔趐romise的中間件的作用)

    攔截器分為請(qǐng)求攔截器和響應(yīng)攔截器详拙,顧名思義: 請(qǐng)求攔截器(interceptors.request)是指可以攔截住每次或指定http請(qǐng)求,并可修改配置項(xiàng) 響應(yīng)攔截器(interceptors.response)可以在每次http請(qǐng)求后攔截住每次或指定http請(qǐng)求蔓同,并可修改返回結(jié)果項(xiàng)饶辙。

    這里先簡(jiǎn)單說(shuō)明,后面會(huì)做詳細(xì)的介紹如何攔截請(qǐng)求響應(yīng)并修改請(qǐng)求參數(shù)修改響應(yīng)數(shù)據(jù)

  • 數(shù)據(jù)轉(zhuǎn)換器 (其實(shí)就是對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換斑粱,比如將對(duì)象轉(zhuǎn)換為JSON字符串)

    數(shù)據(jù)轉(zhuǎn)換器分為請(qǐng)求轉(zhuǎn)換器和響應(yīng)轉(zhuǎn)換器弃揽,顧名思義: 請(qǐng)求轉(zhuǎn)換器(transformRequest)是指在請(qǐng)求前對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換, 響應(yīng)轉(zhuǎn)換器(transformResponse)主要對(duì)請(qǐng)求響應(yīng)后的響應(yīng)體做數(shù)據(jù)轉(zhuǎn)換珊佣。

  • http請(qǐng)求適配器(其實(shí)就是一個(gè)方法)

    在axios項(xiàng)目里,http請(qǐng)求適配器主要指兩種:XHR披粟、http咒锻。 XHR的核心是瀏覽器端的XMLHttpRequest對(duì)象, http核心是node的http[s].request方法

    當(dāng)然守屉,axios也留給了用戶通過(guò)config自行配置適配器的接口的惑艇, 不過(guò),一般情況下拇泛,這兩種適配器就能夠滿足從瀏覽器端向服務(wù)端發(fā)請(qǐng)求或者從node的http客戶端向服務(wù)端發(fā)請(qǐng)求的需求滨巴。

    本次分享主要圍繞XHR。

  • config配置項(xiàng) (其實(shí)就是一個(gè)對(duì)象)

    此處我們說(shuō)的config俺叭,在項(xiàng)目?jī)?nèi)不是真的都叫config這個(gè)變量名恭取,這個(gè)名字是我根據(jù)它的用途起的一個(gè)名字,方便大家理解熄守。

    在axios項(xiàng)目中的蜈垮,設(shè)置\讀取config時(shí), 有的地方叫它defaults(/lib/defaults.js)裕照,這兒是默認(rèn)配置項(xiàng)攒发, 有的地方叫它config,如Axios.prototype.request的參數(shù)晋南,再如xhrAdapterhttp請(qǐng)求適配器方法的參數(shù)惠猿。

    config在axios項(xiàng)目里的是非常重要的一條鏈,是用戶跟axios項(xiàng)目?jī)?nèi)部“通信”的主要橋梁负间。

axios內(nèi)部的運(yùn)作流程圖

工具方法簡(jiǎn)單介紹

(注:本節(jié)可先跳過(guò)偶妖,后面用到了再過(guò)來(lái)查看)

有一些方法在項(xiàng)目中多處使用姜凄,簡(jiǎn)單介紹下這些方法

  1. bind: 給某個(gè)函數(shù)指定上下文,也就是this指向
bind(fn, context); 

實(shí)現(xiàn)效果同Function.prototype.bind方法: fn.bind(context)

  1. forEach:遍歷數(shù)組或?qū)ο?/li>

var utils = require('./utils');
var forEach = utils.forEach;

// 數(shù)組
utils.forEach([], (value, index, array) => {})

// 對(duì)象
utils.forEach({}, (value, key, object) => {})

  1. merge:深度合并多個(gè)對(duì)象為一個(gè)對(duì)象
var utils = require('./utils');
var merge = utils.merge;

var obj1 = {
  a: 1,
  b: {
    bb: 11,
    bbb: 111,
  }
};
var obj2 = {
  a: 2,
  b: {
    bb: 22,
  }
};
var mergedObj = merge(obj1, obj2); 

mergedObj對(duì)象是:

{ 
  a: 2, 
  b: { 
    bb: 22, 
    bbb: 111 
  } 
}
  1. extend:將一個(gè)對(duì)象的方法和屬性擴(kuò)展到另外一個(gè)對(duì)象上餐屎,并指定上下文
var utils = require('./utils');
var extend = utils.extend;

var context = {
  a: 4,
};
var target = {
  k: 'k1',
  fn(){
    console.log(this.a + 1)
  }
};
var source = {
  k: 'k2',
  fn(){
    console.log(this.a - 1)
  }
};
let extendObj = extend(target, source, context);

extendObj對(duì)象是:

{
  k: 'k2',
  fn: source.fn.bind(context),
}

執(zhí)行extendObj.fn();, 打印3

axios為何會(huì)有多種使用方式

如何使用

// 首先將axios包引進(jìn)來(lái)
import axios from 'axios'

第1種使用方式:axios(option)

axios({
  url,
  method,
  headers,
})

第2種使用方式:axios(url[, option])

axios(url, {
  method,
  headers,
})

第3種使用方式(對(duì)于get檀葛、delete等方法):axios[method](url[, option])

axios.get(url, {
  headers,
})

第4種使用方式(對(duì)于post、put等方法):axios[method](url[, data[, option]])

axios.post(url, data, {
  headers,
})

第5種使用方式:axios.request(option)

axios.request({
  url,
  method,
  headers,
})

源碼分析

作為axios項(xiàng)目的入口文件腹缩,我們先來(lái)看下axios.js的源碼
能夠?qū)崿F(xiàn)axios的多種使用方式的核心是createInstance方法:

// /lib/axios.js
function createInstance(defaultConfig) {
  // 創(chuàng)建一個(gè)Axios實(shí)例
  var context = new Axios(defaultConfig);

  // 以下代碼也可以這樣實(shí)現(xiàn):var instance = Axios.prototype.request.bind(context);
  // 這樣instance就指向了request方法屿聋,且上下文指向context,所以可以直接以 instance(option) 方式調(diào)用 
  // Axios.prototype.request 內(nèi)對(duì)第一個(gè)參數(shù)的數(shù)據(jù)類型判斷藏鹊,使我們能夠以 instance(url, option) 方式調(diào)用
  var instance = bind(Axios.prototype.request, context);

  // 把Axios.prototype上的方法擴(kuò)展到instance對(duì)象上润讥,
  // 這樣 instance 就有了 get、post盘寡、put等方法
  // 并指定上下文為context楚殿,這樣執(zhí)行Axios原型鏈上的方法時(shí),this會(huì)指向context
  utils.extend(instance, Axios.prototype, context);

  // 把context對(duì)象上的自身屬性和方法擴(kuò)展到instance上
  // 注:因?yàn)閑xtend內(nèi)部使用的forEach方法對(duì)對(duì)象做for in 遍歷時(shí)竿痰,只遍歷對(duì)象本身的屬性脆粥,而不會(huì)遍歷原型鏈上的屬性
  // 這樣,instance 就有了  defaults影涉、interceptors 屬性变隔。(這兩個(gè)屬性后面我們會(huì)介紹)
  utils.extend(instance, context);

  return instance;
}

// 接收默認(rèn)配置項(xiàng)作為參數(shù)(后面會(huì)介紹配置項(xiàng)),創(chuàng)建一個(gè)Axios實(shí)例蟹倾,最終會(huì)被作為對(duì)象導(dǎo)出
var axios = createInstance(defaults);

以上代碼看上去很繞匣缘,其實(shí)createInstance最終是希望拿到一個(gè)Function,這個(gè)Function指向Axios.prototype.request鲜棠,這個(gè)Function還會(huì)有Axios.prototype上的每個(gè)方法作為靜態(tài)方法肌厨,且這些方法的上下文都是指向同一個(gè)對(duì)象。
那么在來(lái)看看Axios豁陆、Axios.prototype.request的源碼是怎樣的柑爸?
Axiosaxios包的核心,一個(gè)Axios實(shí)例就是一個(gè)axios應(yīng)用盒音,其他方法都是對(duì)Axios內(nèi)容的擴(kuò)展
Axios構(gòu)造函數(shù)的核心方法是request方法竖配,各種axios的調(diào)用方式最終都是通過(guò)request方法發(fā)請(qǐng)求的

// /lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // ...省略代碼
};

// 為支持的請(qǐng)求方法提供別名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

通過(guò)以上代碼,我們就可以以多種方式發(fā)起http請(qǐng)求了: axios()里逆、axios.get()进胯、axios.post()
一般情況,項(xiàng)目使用默認(rèn)導(dǎo)出的axios實(shí)例就可以滿足需求了原押,
如果不滿足需求需要?jiǎng)?chuàng)建新的axios實(shí)例胁镐,axios包也預(yù)留了接口,
看下面的代碼:

// /lib/axios.js  -  31行
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

說(shuō)完axios為什么會(huì)有這么多種使用方式,可能你心中會(huì)有一個(gè)疑問(wèn):
使用axios時(shí)盯漂,無(wú)論get方法還是post方法颇玷,最終都是調(diào)用的Axios.prototype.request方法,那么這個(gè)方法是怎么根據(jù)我們的config配置發(fā)請(qǐng)求的呢就缆?
在開(kāi)始說(shuō)Axios.prototype.request之前帖渠,我們先來(lái)捋一捋在axios項(xiàng)目中,用戶配置的config是怎么起作用的竭宰?

用戶配置的config是怎么起作用的

這里說(shuō)的config空郊,指的是貫穿整個(gè)項(xiàng)目的配置項(xiàng)對(duì)象,
通過(guò)這個(gè)對(duì)象切揭,可以設(shè)置:
http請(qǐng)求適配器狞甚、請(qǐng)求地址、請(qǐng)求方法廓旬、請(qǐng)求頭header哼审、 請(qǐng)求數(shù)據(jù)、請(qǐng)求或響應(yīng)數(shù)據(jù)的轉(zhuǎn)換孕豹、請(qǐng)求進(jìn)度涩盾、http狀態(tài)碼驗(yàn)證規(guī)則、超時(shí)励背、取消請(qǐng)求等
可以發(fā)現(xiàn)春霍,幾乎axios所有的功能都是通過(guò)這個(gè)對(duì)象進(jìn)行配置和傳遞的,
既是axios項(xiàng)目?jī)?nèi)部的溝通橋梁椅野,也是用戶跟axios進(jìn)行溝通的橋梁终畅。
首先我們看看籍胯,用戶能以什么方式定義配置項(xiàng):

import axios from 'axios'

// 第1種:直接修改Axios實(shí)例上defaults屬性竟闪,主要用來(lái)設(shè)置通用配置
axios.defaults[configName] = value;

// 第2種:發(fā)起請(qǐng)求時(shí)最終會(huì)調(diào)用Axios.prototype.request方法,然后傳入配置項(xiàng)杖狼,主要用來(lái)設(shè)置“個(gè)例”配置
axios({
  url,
  method,
  headers,
})

// 第3種:新建一個(gè)Axios實(shí)例炼蛤,傳入配置項(xiàng),此處設(shè)置的是通用配置
let newAxiosInstance = axios.create({
  [configName]: value,
})

看下 Axios.prototype.request 方法里的一行代碼: (/lib/core/Axios.js - 第35行)

config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

可以發(fā)現(xiàn)此處將默認(rèn)配置對(duì)象defaults/lib/defaults.js)蝶涩、Axios實(shí)例屬性this.defaults理朋、request請(qǐng)求的參數(shù)config進(jìn)行了合并。
由此得出绿聘,多處配置的優(yōu)先級(jí)由低到高是:
—> 默認(rèn)配置對(duì)象defaults/lib/defaults.js)
—> { method: 'get' }
—> Axios實(shí)例屬性this.defaults
—> request請(qǐng)求的參數(shù)config
留給大家思考一個(gè)問(wèn)題: defaultsthis.defaults 什么時(shí)候配置是相同的嗽上,什么時(shí)候是不同的?
至此熄攘,我們已經(jīng)得到了將多處merge后的config對(duì)象兽愤,那么這個(gè)對(duì)象在項(xiàng)目中又是怎樣傳遞的呢?

Axios.prototype.request = function request(config) {
  // ...
  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

  var chain = [dispatchRequest, undefined];
  // 將config對(duì)象當(dāng)作參數(shù)傳給Primise.resolve方法
  var promise = Promise.resolve(config);

  // ...省略代碼
  
  while (chain.length) {
    // config會(huì)按序通過(guò) 請(qǐng)求攔截器 - dispatchRequest方法 - 響應(yīng)攔截器
    // 關(guān)于攔截器 和 dispatchRequest方法,下面會(huì)作為一個(gè)專門的小節(jié)來(lái)介紹浅萧。
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

至此逐沙,config走完了它傳奇的一生 -_- 下一節(jié)就要說(shuō)到重頭戲了: Axios.prototype.request

axios.prototype.request

這里面的代碼比較復(fù)雜,一些方法需要追根溯源才能搞清楚洼畅, 所以只需對(duì)chain數(shù)組有個(gè)簡(jiǎn)單的了解就好吩案,涉及到的 攔截器 后面都會(huì)詳細(xì)介紹

chain數(shù)組是用來(lái)盛放攔截器方法和dispatchRequest方法的喻圃, 通過(guò)promisechain數(shù)組里按序取出回調(diào)函數(shù)逐一執(zhí)行顶瞳,最后將處理后的新的promiseAxios.prototype.request方法里返回出去, 并將responseerror傳送出去妒蔚,這就是Axios.prototype.request的使命了己儒。

查看源碼:

// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

此時(shí)崎岂,你一定對(duì)攔截器充滿了好奇,這個(gè)攔截器到底是個(gè)什么家伙闪湾,下一節(jié)就讓我們一探究竟吧

如何攔截請(qǐng)求響應(yīng)并修改請(qǐng)求參數(shù)修改響應(yīng)數(shù)據(jù)

如何使用

// 添加請(qǐng)求攔截器
const myRequestInterceptor = axios.interceptors.request.use(config => {
    // 在發(fā)送http請(qǐng)求之前做些什么
    return config; // 有且必須有一個(gè)config對(duì)象被返回
}, error => {
    // 對(duì)請(qǐng)求錯(cuò)誤做些什么
    return Promise.reject(error);
});

// 添加響應(yīng)攔截器
axios.interceptors.response.use(response => {
  // 對(duì)響應(yīng)數(shù)據(jù)做點(diǎn)什么
  return response; // 有且必須有一個(gè)response對(duì)象被返回
}, error => {
  // 對(duì)響應(yīng)錯(cuò)誤做點(diǎn)什么
  return Promise.reject(error);
});

// 移除某次攔截器
axios.interceptors.request.eject(myRequestInterceptor);

思考

  1. 是否可以直接 return error冲甘?
axios.interceptors.request.use(config => config, error => {
  // 是否可以直接 return error ?
  return Promise.reject(error); 
});

  1. 如何實(shí)現(xiàn)promise的鏈?zhǔn)秸{(diào)用
new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian');

// 打印結(jié)果
// (等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'

源碼分析

關(guān)于攔截器途样,名詞解釋 一節(jié)已經(jīng)做過(guò)簡(jiǎn)單說(shuō)明江醇。

每個(gè)axios實(shí)例都有一個(gè)interceptors實(shí)例屬性, interceptors對(duì)象上有兩個(gè)屬性request何暇、response陶夜。

function Axios(instanceConfig) {
  // ...
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

這兩個(gè)屬性都是一個(gè)InterceptorManager實(shí)例,而這個(gè)InterceptorManager構(gòu)造函數(shù)就是用來(lái)管理攔截器的裆站。

我們先來(lái)看看InterceptorManager構(gòu)造函數(shù):

InterceptorManager構(gòu)造函數(shù)就是用來(lái)實(shí)現(xiàn)攔截器的条辟,這個(gè)構(gòu)造函數(shù)原型上有3個(gè)方法:use、eject宏胯、forEach羽嫡。 關(guān)于源碼,其實(shí)是比較簡(jiǎn)單的肩袍,都是用來(lái)操作該構(gòu)造函數(shù)的handlers實(shí)例屬性的杭棵。

// /lib/core/InterceptorManager.js

function InterceptorManager() {
  this.handlers = []; // 存放攔截器方法,數(shù)組內(nèi)每一項(xiàng)都是有兩個(gè)屬性的對(duì)象氛赐,兩個(gè)屬性分別對(duì)應(yīng)成功和失敗后執(zhí)行的函數(shù)魂爪。
}

// 往攔截器里添加攔截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 用來(lái)注銷指定的攔截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 遍歷this.handlers,并將this.handlers里的每一項(xiàng)作為參數(shù)傳給fn執(zhí)行
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

那么當(dāng)我們通過(guò)axios.interceptors.request.use添加攔截器后艰管, axios內(nèi)部又是怎么讓這些攔截器能夠在請(qǐng)求前滓侍、請(qǐng)求后拿到我們想要的數(shù)據(jù)的呢?

先看下代碼:

// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];

  // 初始化一個(gè)promise對(duì)象牲芋,狀態(tài)微resolved撩笆,接收到的參數(shù)微config對(duì)象
  var promise = Promise.resolve(config);

  // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能為undefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 添加了攔截器后的chain數(shù)組大概會(huì)是這樣的:
  // [
  //   requestFulfilledFn, requestRejectedFn, ..., 
  //   dispatchRequest, undefined,
  //   responseFulfilledFn, responseRejectedFn, ....,
  // ]

  // 只要chain數(shù)組長(zhǎng)度不為0尔破,就一直執(zhí)行while循環(huán)
  while (chain.length) {
    // 數(shù)組的 shift() 方法用于把數(shù)組的第一個(gè)元素從其中刪除,并返回第一個(gè)元素的值浇衬。
    // 每次執(zhí)行while循環(huán)懒构,從chain數(shù)組里按序取出兩項(xiàng),并分別作為promise.then方法的第一個(gè)和第二個(gè)參數(shù)

    // 按照我們使用InterceptorManager.prototype.use添加攔截器的規(guī)則耘擂,正好每次添加的就是我們通過(guò)InterceptorManager.prototype.use方法添加的成功和失敗回調(diào)

    // 通過(guò)InterceptorManager.prototype.use往攔截器數(shù)組里添加攔截器時(shí)使用的數(shù)組的push方法胆剧,
    // 對(duì)于請(qǐng)求攔截器,從攔截器數(shù)組按序讀到后是通過(guò)unshift方法往chain數(shù)組數(shù)里添加的醉冤,又通過(guò)shift方法從chain數(shù)組里取出的秩霍,所以得出結(jié)論:對(duì)于請(qǐng)求攔截器,先添加的攔截器會(huì)后執(zhí)行
    // 對(duì)于響應(yīng)攔截器蚁阳,從攔截器數(shù)組按序讀到后是通過(guò)push方法往chain數(shù)組里添加的铃绒,又通過(guò)shift方法從chain數(shù)組里取出的,所以得出結(jié)論:對(duì)于響應(yīng)攔截器螺捐,添加的攔截器先執(zhí)行

    // 第一個(gè)請(qǐng)求攔截器的fulfilled函數(shù)會(huì)接收到promise對(duì)象初始化時(shí)傳入的config對(duì)象颠悬,而請(qǐng)求攔截器又規(guī)定用戶寫的fulfilled函數(shù)必須返回一個(gè)config對(duì)象,所以通過(guò)promise實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用時(shí)定血,每個(gè)請(qǐng)求攔截器的fulfilled函數(shù)都會(huì)接收到一個(gè)config對(duì)象

    // 第一個(gè)響應(yīng)攔截器的fulfilled函數(shù)會(huì)接受到dispatchRequest(也就是我們的請(qǐng)求方法)請(qǐng)求到的數(shù)據(jù)(也就是response對(duì)象),而響應(yīng)攔截器又規(guī)定用戶寫的fulfilled函數(shù)必須返回一個(gè)response對(duì)象赔癌,所以通過(guò)promise實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用時(shí),每個(gè)響應(yīng)攔截器的fulfilled函數(shù)都會(huì)接收到一個(gè)response對(duì)象

    // 任何一個(gè)攔截器的拋出的錯(cuò)誤澜沟,都會(huì)被下一個(gè)攔截器的rejected函數(shù)收到灾票,所以dispatchRequest拋出的錯(cuò)誤才會(huì)被響應(yīng)攔截器接收到。

    // 因?yàn)閍xios是通過(guò)promise實(shí)現(xiàn)的鏈?zhǔn)秸{(diào)用茫虽,所以我們可以在攔截器里進(jìn)行異步操作刊苍,而攔截器的執(zhí)行順序還是會(huì)按照我們上面說(shuō)的順序執(zhí)行,也就是 dispatchRequest 方法一定會(huì)等待所有的請(qǐng)求攔截器執(zhí)行完后再開(kāi)始執(zhí)行濒析,響應(yīng)攔截器一定會(huì)等待 dispatchRequest 執(zhí)行完后再開(kāi)始執(zhí)行正什。

    promise = promise.then(chain.shift(), chain.shift());

  }

  return promise;
};

現(xiàn)在,你應(yīng)該已經(jīng)清楚了攔截器是怎么回事悼枢,以及攔截器是如何在Axios.prototype.request方法里發(fā)揮作用的了埠忘, 那么處于"中游位置"的dispatchRequest是如何發(fā)送http請(qǐng)求的呢脾拆?

dispatchrequest都做了哪些事

dispatchRequest主要做了3件事: 1馒索,拿到config對(duì)象,對(duì)config進(jìn)行傳給http請(qǐng)求適配器前的最后處理名船; 2绰上,http請(qǐng)求適配器根據(jù)config配置,發(fā)起請(qǐng)求 3渠驼,http請(qǐng)求適配器請(qǐng)求完成后蜈块,如果成功則根據(jù)header、data、和config.transformResponse(關(guān)于transformResponse百揭,下面的 數(shù)據(jù)轉(zhuǎn)換器 會(huì)進(jìn)行講解)拿到數(shù)據(jù)轉(zhuǎn)換后的response爽哎,并return。

// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Ensure headers exist
  config.headers = config.headers || {};

  // 對(duì)請(qǐng)求data進(jìn)行轉(zhuǎn)換
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 對(duì)header進(jìn)行合并處理
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 刪除header屬性里無(wú)用的屬性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // http請(qǐng)求適配器會(huì)優(yōu)先使用config上自定義的適配器器一,沒(méi)有配置時(shí)才會(huì)使用默認(rèn)的XHR或http適配器课锌,不過(guò)大部分時(shí)候,axios提供的默認(rèn)適配器是能夠滿足我們的
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(/**/);
};

好了祈秕,看到這里渺贤,我們是時(shí)候梳理一下:axios是如何用promise搭起基于xhr的異步橋梁的?

axios是如何用promise搭起基于xhr的異步橋梁的

axios是如何通過(guò)Promise進(jìn)行異步處理的请毛?

如何使用

import axios from 'axios'

axios.get(/**/)
.then(data => {
  // 此處可以拿到向服務(wù)端請(qǐng)求回的數(shù)據(jù)
})
.catch(error => {
  // 此處可以拿到請(qǐng)求失敗或取消或其他處理失敗的錯(cuò)誤對(duì)象
})

源碼分析

先來(lái)一個(gè)圖簡(jiǎn)單的了解下axios項(xiàng)目里志鞍,http請(qǐng)求完成后到達(dá)用戶的順序流:

image.png

通過(guò) axios為何會(huì)有多種使用方式 我們知道, 用戶無(wú)論以什么方式調(diào)用axios方仿,最終都是調(diào)用的Axios.prototype.request方法固棚, 這個(gè)方法最終返回的是一個(gè)Promise對(duì)象。

Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  // 將config對(duì)象當(dāng)作參數(shù)傳給Primise.resolve方法
  var promise = Promise.resolve(config);

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Axios.prototype.request方法會(huì)調(diào)用dispatchRequest方法仙蚜,而dispatchRequest方法會(huì)調(diào)用xhrAdapter方法玻孟,xhrAdapter方法返回的是還一個(gè)Promise對(duì)象

// /lib/adapters/xhr.js
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // ... 省略代碼
  });
};

xhrAdapter內(nèi)的XHR發(fā)送請(qǐng)求成功后會(huì)執(zhí)行這個(gè)Promise對(duì)象的resolve方法,并將請(qǐng)求的數(shù)據(jù)傳出去, 反之則執(zhí)行reject方法,并將錯(cuò)誤信息作為參數(shù)傳出去鳍征。

// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

request[loadEvent] = function handleLoad() {
  // ...
  // 往下走有settle的源碼
  settle(resolve, reject, response);
  // ...
};
request.onerror = function handleError() {
  reject(/**/);
  request = null;
};
request.ontimeout = function handleTimeout() {
  reject(/**/);
  request = null;
};

驗(yàn)證服務(wù)端的返回結(jié)果是否通過(guò)驗(yàn)證:

// /lib/core/settle.js
function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(/**/);
  }
};

回到dispatchRequest方法內(nèi)黍翎,首先得到xhrAdapter方法返回的Promise對(duì)象, 然后通過(guò).then方法,對(duì)xhrAdapter返回的Promise對(duì)象的成功或失敗結(jié)果再次加工艳丛, 成功的話匣掸,則將處理后的response返回, 失敗的話氮双,則返回一個(gè)狀態(tài)為rejected的Promise對(duì)象碰酝,

  return adapter(config).then(function onAdapterResolution(response) {
    // ...
    return response;
  }, function onAdapterRejection(reason) {
    // ...
    return Promise.reject(reason);
  });
};

那么至此,用戶調(diào)用axios()方法時(shí)戴差,就可以直接調(diào)用Promise的.then.catch進(jìn)行業(yè)務(wù)處理了送爸。

回過(guò)頭來(lái),我們?cè)诮榻BdispatchRequest一節(jié)時(shí)說(shuō)到的數(shù)據(jù)轉(zhuǎn)換暖释,而axios官方也將數(shù)據(jù)轉(zhuǎn)換專門作為一個(gè)亮點(diǎn)來(lái)介紹的袭厂,那么數(shù)據(jù)轉(zhuǎn)換到底能在使用axios發(fā)揮什么功效呢?

數(shù)據(jù)轉(zhuǎn)換器-轉(zhuǎn)換請(qǐng)求與響應(yīng)數(shù)據(jù)

如何使用

  1. 修改全局的轉(zhuǎn)換器
import axios from 'axios'

// 往現(xiàn)有的請(qǐng)求轉(zhuǎn)換器里增加轉(zhuǎn)換方法
axios.defaults.transformRequest.push((data, headers) => {
  // ...處理data
  return data;
});

// 重寫請(qǐng)求轉(zhuǎn)換器
axios.defaults.transformRequest = [(data, headers) => {
  // ...處理data
  return data;
}];

// 往現(xiàn)有的響應(yīng)轉(zhuǎn)換器里增加轉(zhuǎn)換方法
axios.defaults.transformResponse.push((data, headers) => {
  // ...處理data
  return data;
});

// 重寫響應(yīng)轉(zhuǎn)換器
axios.defaults.transformResponse = [(data, headers) => {
  // ...處理data
  return data;
}];
  1. 修改某次axios請(qǐng)求的轉(zhuǎn)換器
import axios from 'axios'

// 往已經(jīng)存在的轉(zhuǎn)換器里增加轉(zhuǎn)換方法
axios.get(url, {
  // ...
  transformRequest: [
    ...axios.defaults.transformRequest, // 去掉這行代碼就等于重寫請(qǐng)求轉(zhuǎn)換器了
    (data, headers) => {
      // ...處理data
      return data;
    }
  ],
  transformResponse: [
    ...axios.defaults.transformResponse, // 去掉這行代碼就等于重寫響應(yīng)轉(zhuǎn)換器了
    (data, headers) => {
      // ...處理data
      return data;
    }
  ],
})

源碼分析

默認(rèn)的defaults配置項(xiàng)里已經(jīng)自定義了一個(gè)請(qǐng)求轉(zhuǎn)換器和一個(gè)響應(yīng)轉(zhuǎn)換器球匕, 看下源碼:

// /lib/defaults.js
var defaults = {

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Content-Type');
    // ...
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

};

那么在axios項(xiàng)目里纹磺,是在什么地方使用了轉(zhuǎn)換器呢?

請(qǐng)求轉(zhuǎn)換器的使用地方是http請(qǐng)求前亮曹,使用請(qǐng)求轉(zhuǎn)換器對(duì)請(qǐng)求數(shù)據(jù)做處理橄杨, 然后傳給http請(qǐng)求適配器使用秘症。

// /lib/core/dispatchRequest.js
function dispatchRequest(config) {

  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  return adapter(config).then(/* ... */);
};

看下transformData方法的代碼, 主要遍歷轉(zhuǎn)換器數(shù)組式矫,分別執(zhí)行每一個(gè)轉(zhuǎn)換器乡摹,根據(jù)data和headers參數(shù),返回新的data采转。

// /lib/core/transformData.js
function transformData(data, headers, fns) {
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });
  return data;
};

響應(yīng)轉(zhuǎn)換器的使用地方是在http請(qǐng)求完成后趟卸,根據(jù)http請(qǐng)求適配器的返回值做數(shù)據(jù)轉(zhuǎn)換處理:

// /lib/core/dispatchRequest.js
return adapter(config).then(function onAdapterResolution(response) {
    // ...
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      // ...
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });

轉(zhuǎn)換器和攔截器的關(guān)系?

攔截器同樣可以實(shí)現(xiàn)轉(zhuǎn)換請(qǐng)求和響應(yīng)數(shù)據(jù)的需求氏义,但根據(jù)作者的設(shè)計(jì)和綜合代碼可以看出锄列, 在請(qǐng)求時(shí),攔截器主要負(fù)責(zé)修改config配置項(xiàng)惯悠,數(shù)據(jù)轉(zhuǎn)換器主要負(fù)責(zé)轉(zhuǎn)換請(qǐng)求體邻邮,比如轉(zhuǎn)換對(duì)象為字符串 在請(qǐng)求響應(yīng)后,攔截器可以拿到response克婶,數(shù)據(jù)轉(zhuǎn)換器主要負(fù)責(zé)處理響應(yīng)體筒严,比如轉(zhuǎn)換字符串為對(duì)象。

axios官方是將"自動(dòng)轉(zhuǎn)換為JSON數(shù)據(jù)"作為一個(gè)獨(dú)立的亮點(diǎn)來(lái)介紹的情萤,那么數(shù)據(jù)轉(zhuǎn)換器是如何完成這個(gè)功能的呢鸭蛙? 其實(shí)非常簡(jiǎn)單,我們一起看下吧筋岛。

自動(dòng)轉(zhuǎn)換json數(shù)據(jù)

在默認(rèn)情況下娶视,axios將會(huì)自動(dòng)的將傳入的data對(duì)象序列化為JSON字符串,將響應(yīng)數(shù)據(jù)中的JSON字符串轉(zhuǎn)換為JavaScript對(duì)象

源碼分析

// 請(qǐng)求時(shí)睁宰,將data數(shù)據(jù)轉(zhuǎn)換為JSON 字符串
// /lib/defaults.js 
transformRequest: [function transformRequest(data, headers) {
  // ...
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
}]

// 得到響應(yīng)后肪获,將請(qǐng)求到的數(shù)據(jù)轉(zhuǎn)換為JSON對(duì)象
// /lib/defaults.js
transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
}]

至此,axios項(xiàng)目的運(yùn)作流程已經(jīng)介紹完畢柒傻,是不是已經(jīng)打通了任督二脈了呢 接下來(lái)我們一起看下axios還帶給了我們哪些好用的技能點(diǎn)吧孝赫。

header設(shè)置

如何使用

import axios from 'axios'

// 設(shè)置通用header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // xhr標(biāo)識(shí)

// 設(shè)置某種請(qǐng)求的header
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

// 設(shè)置某次請(qǐng)求的header
axios.get(url, {
  headers: {
    'Authorization': 'whr1',
  },
})

源碼分析

// /lib/core/dispatchRequest.js  -  44行

  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

如何取消已經(jīng)發(fā)送的請(qǐng)求

如何使用

import axios from 'axios'

// 第一種取消方法
axios.get(url, {
  cancelToken: new axios.CancelToken(cancel => {
    if (/* 取消條件 */) {
      cancel('取消日志');
    }
  })
});

// 第二種取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
  cancelToken: source.token
});
source.cancel('取消日志');

源碼分析

// /cancel/CancelToken.js  -  11行
function CancelToken(executor) {

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

// /lib/adapters/xhr.js  -  159行
if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }
        request.abort();
        reject(cancel);
        request = null;
    });
}

取消功能的核心是通過(guò)CancelToken內(nèi)的this.promise = new Promise(resolve => resolvePromise = resolve), 得到實(shí)例屬性promise红符,此時(shí)該promise的狀態(tài)為pending 通過(guò)這個(gè)屬性青柄,在/lib/adapters/xhr.js文件中繼續(xù)給這個(gè)promise實(shí)例添加.then方法 (xhr.js文件的159行config.cancelToken.promise.then(message => request.abort()));

CancelToken外界预侯,通過(guò)executor參數(shù)拿到對(duì)cancel方法的控制權(quán)致开, 這樣當(dāng)執(zhí)行cancel方法時(shí)就可以改變實(shí)例的promise屬性的狀態(tài)為rejected, 從而執(zhí)行request.abort()方法達(dá)到取消請(qǐng)求的目的雌桑。

上面第二種寫法可以看作是對(duì)第一種寫法的完善喇喉, 因?yàn)楹芏嗍菚r(shí)候我們?nèi)∠?qǐng)求的方法是用在本次請(qǐng)求方法外祖今, 例如校坑,發(fā)送A拣技、B兩個(gè)請(qǐng)求,當(dāng)B請(qǐng)求成功后耍目,取消A請(qǐng)求膏斤。

// 第1種寫法:
let source;
axios.get(Aurl, {
  cancelToken: new axios.CancelToken(cancel => {
    source = cancel;
  })
});
axios.get(Burl)
.then(() => source('B請(qǐng)求成功了'));

// 第2種寫法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
  cancelToken: source.token
});
axios.get(Burl)
.then(() => source.cancel('B請(qǐng)求成功了'));

相對(duì)來(lái)說(shuō),我更推崇第1種寫法邪驮,因?yàn)榈?種寫法太隱蔽了莫辨,不如第一種直觀好理解。

發(fā)現(xiàn)的問(wèn)題
  1. /lib/adapters/xhr.js文件中毅访,onCanceled方法的參數(shù)不應(yīng)該叫message么沮榜,為什么叫cancel?

  2. /lib/adapters/xhr.js文件中喻粹,onCanceled方法里蟆融,reject里應(yīng)該將config信息也傳出來(lái)

跨域攜帶cookie

如何使用


import axios from 'axios'

axios.defaults.withCredentials = true;

源碼分析

我們?cè)?用戶配置的config是怎么起作用的 一節(jié)已經(jīng)介紹了config在axios項(xiàng)目里的傳遞過(guò)程, 由此得出守呜,我們通過(guò)axios.defaults.withCredentials = true做的配置型酥, 在/lib/adapters/xhr.js里是可以取到的,然后通過(guò)以下代碼配置到xhr對(duì)象項(xiàng)查乒。

var request = new XMLHttpRequest();

// /lib/adapters/xhr.js
if (config.withCredentials) {
  request.withCredentials = true;
}

超時(shí)配置及處理

如何使用

import axios from 'axios'

axios.defaults.timeout = 3000;

源碼分析

// /adapters/xhr.js
request.timeout = config.timeout;

// /adapters/xhr.js
// 通過(guò)createError方法弥喉,將錯(cuò)誤信息合為一個(gè)字符串
request.ontimeout = function handleTimeout() {
  reject(createError('timeout of ' + config.timeout + 'ms exceeded', 
    config, 'ECONNABORTED', request));
};

axios庫(kù)外如何添加超時(shí)后的處理

axios().catch(error => {
  const { message } = error;
  if (message.indexOf('timeout') > -1){
    // 超時(shí)處理
  }
})

改寫驗(yàn)證成功或失敗的規(guī)則validatestatus

自定義http狀態(tài)碼的成功、失敗范圍

如何使用

import axios from 'axios'

axios.defaults.validateStatus = status => status >= 200 && status < 300;

源碼分析

在默認(rèn)配置中玛迄,定義了默認(rèn)的http狀態(tài)碼驗(yàn)證規(guī)則由境, 所以自定義validateStatus其實(shí)是對(duì)此處方法的重寫

// `/lib/defaults.js`
var defaults = {
  // ...
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  },
  // ...
}

axios是何時(shí)開(kāi)始驗(yàn)證http狀態(tài)碼的?

// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

// /lib/adapters/xhr.js
// 每當(dāng) readyState 改變時(shí)蓖议,就會(huì)觸發(fā) onreadystatechange 事件
request[loadEvent] = function handleLoad() {
  if (!request || (request.readyState !== 4 && !xDomain)) {
    return;
  }
  // ...省略代碼
  var response = {
      // ...
      // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
      status: request.status === 1223 ? 204 : request.status,
      config: config,
  };
  settle(resolve, reject, response);
  // ...省略代碼
}
// /lib/core/settle.js
function settle(resolve, reject, response) {
  // 如果我們往上搗一搗就會(huì)發(fā)現(xiàn)藻肄,config對(duì)象的validateStatus就是我們自定義的validateStatus方法或默認(rèn)的validateStatus方法
  var validateStatus = response.config.validateStatus;
  // validateStatus驗(yàn)證通過(guò),就會(huì)觸發(fā)resolve方法
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};

總結(jié)

axios這個(gè)項(xiàng)目里拒担,有很多對(duì)JS使用很巧妙的地方嘹屯,比如對(duì)promise的串聯(lián)操作(當(dāng)然你也可以說(shuō)這塊是借鑒很多異步中間件的處理方式),讓我們可以很方便對(duì)請(qǐng)求前后的各種處理方法的流程進(jìn)行控制;很多實(shí)用的小優(yōu)化从撼,比如請(qǐng)求前后的數(shù)據(jù)處理州弟,省了程序員一遍一遍去寫JSON.xxx了;同時(shí)支持了瀏覽器和node兩種環(huán)境低零,對(duì)使用node的項(xiàng)目來(lái)說(shuō)無(wú)疑是極好的婆翔。

來(lái)源:https://juejin.im/post/5b0ba2d56fb9a00a1357a334

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掏婶,隨后出現(xiàn)的幾起案子啃奴,更是在濱河造成了極大的恐慌,老刑警劉巖雄妥,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件最蕾,死亡現(xiàn)場(chǎng)離奇詭異依溯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瘟则,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門黎炉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人醋拧,你說(shuō)我怎么就攤上這事慷嗜。” “怎么了丹壕?”我有些...
    開(kāi)封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵庆械,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我菌赖,道長(zhǎng)干奢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任盏袄,我火速辦了婚禮忿峻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辕羽。我一直安慰自己逛尚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布刁愿。 她就那樣靜靜地躺著绰寞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铣口。 梳的紋絲不亂的頭發(fā)上滤钱,一...
    開(kāi)封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音脑题,去河邊找鬼件缸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叔遂,可吹牛的內(nèi)容都是我干的他炊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼已艰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痊末!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起哩掺,我...
    開(kāi)封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凿叠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盒件,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹬碧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了履恩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锰茉。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呢蔫,死狀恐怖切心,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情片吊,我是刑警寧澤绽昏,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站俏脊,受9級(jí)特大地震影響全谤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爷贫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一认然、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漫萄,春花似錦卷员、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至岩瘦,卻和暖如春未巫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背启昧。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工叙凡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人密末。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓狭姨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親苏遥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饼拍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345