手把手教你使用ts 一步一步的去完成一個Promise

promise

前奏

筆者公司前端小組,線上出現了由於Promise用錯了而致使的問題。 本文的出發點不只是瞭解Promise,主要目的是跟着特性去寫一個Promise。 因此前提是你已經掌握了Promise的基本特性,若是沒有接觸過的話,請點擊學習前端

正文

熟悉es6的Promise特性。

1.特性概覽

  • 鏈式調用。git

  • 內部三種狀態。es6

內部三種狀態分別爲pending、fullfiled、rejected,初始化狀態爲pending。狀態變化能夠從pending轉化fullfiled或rejected。無其餘轉化方式。github

2.出現的背景

解決ajax回調地獄。ajax

ajax回調地獄,本質是但願不可控制的異步變成同步形式。如:npm

ajax1({...,success(res1){
    ...
}})

ajax2({...,params:{res1},success(res2){ // error no res1
    ...
}})
複製代碼

當咱們的ajax2須要用到ajax1的時候,咱們不得不使用嵌套式:數組

ajax1({...,success(res1){
    ajax2({...,params:{res1},success(res2){
        /// doing something
    }})
}})
複製代碼

這種寫法的最大問題就是當嵌套層數不少當時候,代碼會變得難以維護。promise

那麼 how is Promise的寫法 ajax改造:bash

p1 = function() {
  return  new Promise(function(resolve){
        ajax1({...,success(res1){
            resolve(res1)
        }})
    })
}

p2 = function(){
   return new Promise(function(resolve){
        ajax2({...,params:{res1},success(res2){
            /// doing something
        }})
    })
}
複製代碼

那麼最終的寫法則變成markdown

p1().then(res=>{
    return p2()
}).then(res2=>{
    // doing something
})
複製代碼

3 具體特性。

  • 1.瞭解特性首推官方的Promise A+,點擊
  • 2.而後是用法,阮一峯大大的es6 Promise特別詳細。點擊

這裏根據Promise A+ 把接下來的幾個定義函數作在約定。

1.new Promise().then(),傳遞的函數分別叫 onFulfilled、onRejected

開始手寫

1.Promise是個函數

因此咱們最早開始的就是傳遞一個函數

function Promise(executor) {
    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }
}
複製代碼

2.Promise 初始狀態。

promise有三種狀態,能夠用ts的枚舉

enum pStatus  {
    pending = 'pending',
    fulled = 'fullfilled',
    rejected = 'rejected'
}
複製代碼

而後咱們須要定義一些屬性。

function Promise() {
    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending; // 默認狀態

    this.resovlecbs = []; // 回調的resolve函數  主要來自於Promise.prototype.then
    this.rejectcbs = []; //  回調的reject函數  主要來自於Promise.prototype.then
    this.value; // 記錄的resolve值
    this.error; // 記錄的reject值
}
複製代碼

咱們知道Promise傳遞的函數,是直接會在主線程的執行的,因此咱們須要直接執行它。

function Promise() {
    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending; // 默認狀態

    this.resovlecbs = []; // 回調的resolve函數  主要來自於Promise.prototype.then
    this.rejectcbs = []; //  回調的reject函數  主要來自於Promise.prototype.then
    this.value; // 記錄的resolve值
    this.error; // 記錄的reject值
    
    try {
        executor(resolve,reject); // 傳遞的函數的執行。
    } catch (error) {
        reject(error); // 捕獲的異常會直接執行reject。
    }
}
複製代碼

3.開始Promise的鏈式結構,達到異步結果變同步,解決回調地獄。

首先須要說明的是鏈式的結構的原理是不斷返回新的Promise,也就是說then的結果是

Promise.prototype.then = function() {
    return new Promise(function(resolve,reject){
        xxx...
    })
}
複製代碼

首先咱們須要明確,Promise具體特性是什麼?

1.then 傳入的值分別是 resolve的回調和 reject狀態回調。

2.傳遞值,將上一個then的值一直往下傳。

3.符合同層先來先到,異層一定上層先執行的策略。

爲了瞭解第三個特性的詳細意思以前,讓咱們看一個例子:

var p1 = new Promise(function(resolve,reject){
    resolve('p1')
});

var p2 = new Promise(function(resolve,reject){
    resolve('p2')
});

p1.then(()=>{
    console.log('p11')
}).then(()=>{
    console.log('p12')
})

p2.then(()=>{
    console.log('p21')
}).then(()=>{
    console.log('p22')
})
複製代碼

相信你們都知道順序爲 p11 => p21 => p12 => p22。緣由的話涉及到宏微任務的特性,請參考這篇文章,點擊學習。 想必你們已經明白第三點了。

那麼如何實現?

分步驟:

1) 傳遞給Promise的函數,完成後咱們纔會執行then傳遞的函數。也就是

new Promise(function(resolve,reject){
    resolve('xxx') // (1)只有執行完這個纔會執行後面then的  onFulfilled函數
}).then(function(){
    ...xxx  // (2)這是第二步
})
複製代碼

因此then傳遞onFulfilled函數和onRejected函數都是在resolve中執行的。因此then其實只是去保存then傳遞的函數而已,而保存的地方則是Promise主函數內部的resolvecbs和rejectcbs這兩個數組。

我以爲可能會有人問爲何會是數組?

由於你可能會這麼寫:

var p = new Promise(...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
複製代碼

這種非鏈路,其實都是把onFullfilled,保存到Promise內部,因此須要數組。

而後就是Promise內部到resolve函數和reject函數。這兩個函數會作爲 用戶傳入的函數的參數傳入。 本質內部就是去遍歷執行reslovecbs的函數項。而且改變狀態,還有就是將傳入的值記錄下來,這些值會傳給onFullfilled,並由onFullfilled決定是否要繼續傳遞下去。也便是:

then(function onFullfilled(value){ // value 來自於resolve傳遞的參數。
    return value  // return 則表示續傳  下一個then是否能拿到。
})
複製代碼

咱們添加下,大體以下:

function Promise() {
    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending; // 默認狀態

    this.resovlecbs = []; // 回調的resolve函數  主要來自於Promise.prototype.then
    this.rejectcbs = []; //  回調的reject函數  主要來自於Promise.prototype.then
    this.value; // 記錄的resolve值
    this.error; // 記錄的reject值
    
    const resolve = (value:object)=>{ // resolve作的三件事
        this.value = value; // 記錄值  then 的  onFullfilled會用它
        
        this.resovlecbs.forEach((item:Function)=>{
            item(value); // 這個就是  onFullfilled函數,會用上面的value
        })
        this.status = pStatus.fulled; // 把狀態改變爲 fullfilled

    }
    
    // ... reject同理
    
    try {
        executor(resolve,reject);
    } catch (error) {
        reject(error);
    }
}
複製代碼

resolve中執行的是resolveCbs數組存放的函數。而這些函數是來自於then推送的。 可是值得注意的是,函數除了執行then傳遞的onFullfiled函數和onRejected函數,還要將這兩個返回的值,傳遞下去,因此要執行下一個Promise的resolve,由於resolve的第一個特性就是記錄值。 因此then是這樣的。

Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {

    let scope = this;
    return new Promise(function(resolve = noop,reject = noop){
         scope.resovlecbs.push((value)=>{
            handlerRes(onFullfilled,value,resolve);
        })
        scope.rejectcbs.push((error)=>{
            handlerRes(onRejected,error,reject);
        })
    });
}

export function handlerRes(handler,message,next){
    let res 
    if(isFunc(handler)){
        res = handler(message);
    }
    next(res); // 執行下一個函數的resolve
}
複製代碼

能夠看到這裏是把 then傳遞的函數onFullfilled和onRejected分別推入 實例的 resovlecbs 數組和 rejectcbs()達到resolve和onFullfilled的同步執行的效果。

且不只是onRresolved被執行,同時被執行的還有下一個Promise的 resolve。

這樣已經實現了then 鏈的順序執行了。

對於構造函數new Promise(),的幾個步驟是 建立一個空對象,並將Promise內部執行的全部屬性都掛載到這個對象上。也就是this的全部屬性。

傳遞都效果圖以下:

可是上面的寫法會有兩個問題:

  • 1.沒法達到前面說的符合同層先來先到,異層一定上層先執行的策略。,這種效果,正式event loop的隊列。 因此咱們可使用微任務或宏任務。 這裏是了簡化代碼結構使用setTimeout來模擬,若是感興趣能夠去了解下這個npm庫asap,點擊這裏

  • 2.目前咱們的then函數的寫法是直接把函數推入到resolvecbs數組,等待resolve去執行,可是這種方式不hack,若是咱們先執行了resolve後,咱們在執行then。好比:

var p1 = new Promise(function(resolve,reject){
    resolve('p1')
});

p1.then(()=>{
    console.log('p11')
}).
複製代碼

這時候咱們會先執行resolve, 完成了resolvecbs的遍歷執行,而後纔去經過then,對resolvecbs進行蒐集。name後面蒐集的函數就永遠不會執行了。因此咱們必須判斷狀態。

hack寫法:

Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {


    let scope = this;
    return new Promise(function(resolve = noop,reject = noop){
        if(scope.status === pStatus.pending) { // pending則等待執行
            scope.resovlecbs.push((value)=>{
                handlerRes(onFullfilled,value,resolve);
            })
            scope.rejectcbs.push((error)=>{
                handlerRes(onRejected,error,reject);
            })
        } else if(scope.status===pStatus.fulled) { // fullfilled則直接執行
            handlerRes(onFullfilled,scope.value,resolve);
        } else { // rejectd 直接執行
            handlerRes(onRejected,scope.error,reject);
        }
    });
}
複製代碼

這裏將多種狀況考慮進入了,然而你以爲已經結束了嗎? 咱們來看下Promise A+怎麼說的。

翻譯下就是:

  • 若是 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值。
  • 若是 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的值。

什麼意思? 讓咱們看一個例子:

new Promise(function(resolve){
    resolve('test')
}).then().then(value=>{
    console.log(value)
})
複製代碼

上述狀況,會傳遞嗎?

答案是會。 這是Promise A+的標準。 因此咱們在then中必須兼容了。讓咱們再次改造下then函數。

Promise.prototype.then = function (onFullfilled:Function,onRejected:Function) {

    let scope = this;
    return new Promise(function(resolve = noop,reject = noop){

        const resolveHandler = function(value){
            if(isFunc(onFullfilled)) {
                handlerRes(onFullfilled,value,resolve);
            } else {
                resolve(value)
            }
        }
        const rejectHanlder = function(error) {
            if(isFunc(onRejected)){
                handlerRes(onRejected,error,resolve);
            } else {
                reject(error);
            }
        }

        try {
            if(scope.status === pStatus.pending) {
                scope.resovlecbs.push((value)=>{
                    resolveHandler(value)
                })
                scope.rejectcbs.push((error)=>{
                    rejectHanlder(error);
                })
            } else if(scope.status===pStatus.fulled) {
                resolveHandler(scope.value);
            } else { // rejectd
                rejectHanlder(scope.error);
            }
        } catch (error) {
            reject(error);
        }
    });
}
function handlerRes(handler,message,next){
    let res 
    if(isFunc(handler)){
        res = handler(message);
    }
    next(res); // 執行下一個函數的resolve
}
複製代碼

好了,到了這裏基本已經完成大部分了,然而,關於回調地獄的問題依然沒有解決。讓咱們看看咱們是怎麼處理回調地獄的。

promise1.then(function(){
    return promise2
}).then(value=>{
    console.log(value) // 應該須要拿到promise2的結果
})
複製代碼

目前咱們處理then的onFullfilled函數的結果是在handlerRes這個函數中進行的,因此咱們必須對這個函數進行改造,來適應return類型爲promise的處理。

function handlerRes(handler,message,nextResolve,nextReject,Promise){
    let res 
    if(isFunc(handler)){
        res = handler(message);
    }
    
    if(res && res instanceof Promise) {
        if(res.status===pStatus.pending){
            res.then(value=>{
                nextResolve(value)
            },err=>{
                nextReject(err)
            })
        }
    } else {
        nextResolve(res);
    }
}
複製代碼

上面已經添加了對Promise的處理,這樣ok了嗎? 若是promise2也是個深層次的promise則會出問題。 如promise2.then(value=>{}); value若是是promise的實例, 這個時候,咱們的handlerRes仍是會問題的。

因此咱們須要遞歸的處理,讓咱們改造下:

export function deepGet(res,Promise2,nextResolve,nextReject){
    if(res && res instanceof Promise2) {
        if(res.status===pStatus.pending){
            res.then(value=>{
                deepGet(value,Promise2,nextResolve,nextReject)
            },err=>{
                nextReject(err)
            })
        }
    } else {
        nextResolve(res);
    }
}

export function handlerRes(handler,message,nextResolve,nextReject,Promise2){
    let res 
    if(isFunc(handler)){
        res = handler(message);
    }
    deepGet(res,Promise2,nextResolve,nextReject)
}
複製代碼

完整的代碼示例以下:

Promise2.prototype.then = function (onFullfilled:Function,onRejected:Function) {

    let scope = this;
    return new Promise2(function(resolve = noop,reject = noop){

        const resolveHandler = function(value){
            if(isFunc(onFullfilled)) {
                handlerRes(onFullfilled,value,resolve,reject,scope.constructor);
            } else {
                resolve(value)
            }
        }
        const rejectHanlder = function(error) {
            if(isFunc(onRejected)){
                handlerRes(onRejected,error,resolve,reject,scope.constructor);
            } else {
                reject(error);
            }
        }

        try {
            if(scope.status === pStatus.pending) {
                scope.resovlecbs.push((value)=>{
                    resolveHandler(value)
                })
                scope.rejectcbs.push((error)=>{
                    rejectHanlder(error);
                })
            } else if(scope.status===pStatus.fulled) {
                resolveHandler(scope.value);
            } else { // rejectd
                rejectHanlder(scope.error);
            }
        } catch (error) {
            reject(error);
        }
    });
}

export function deepGet(res,Promise2,nextResolve,nextReject){
    if(res && res instanceof Promise2) {
        if(res.status===pStatus.pending){
            res.then(value=>{
                deepGet(value,Promise2,nextResolve,nextReject)
            },err=>{
                nextReject(err)
            })
        }
    } else {
        nextResolve(res);
    }
}

export function handlerRes(handler,message,nextResolve,nextReject,Promise2){
    let res 
    if(isFunc(handler)){
        res = handler(message);
    }
    deepGet(res,Promise2,nextResolve,nextReject)
}
複製代碼

then函數的編寫完成了,讓咱們再回來看看Promise自己。

Promise是微任務,這裏爲了方便, 對Promise自己添加宏任務間隔。reject同理。

function Promise(executor:any) {

    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending;

    this.resovlecbs = [];
    this.rejectcbs = [];
    this.value;
    this.error;

    const resolve = (value:object)=>{
        this.value = value;
        setTimeout(()=>{
            this.resovlecbs.forEach((item:Function)=>{
                item(value);
            })
            this.status = pStatus.fulled;
        },0)
    }

    const reject = (error:Error)=>{
        this.error = error;

        setTimeout(()=>{ 
            this.status = pStatus.rejected;
            if(this.rejectcbs.length ===0){
                throw this.error;
            }  else {
                this.rejectcbs.forEach((item:Function)=>{
                    item(error);
                })
            }
        },0)
       // if(this.rejectcbs.length === 0 ) throw error;
    } 

    try {
        executor(resolve,reject);
    } catch (error) {
        reject(error);
    }
}
複製代碼

然而這依然並不是是最終版本,由於這沒法解決,屢次resolve會重複執行 resolvecbs的問題。 因此resolve函數的內容必須旨在pending的狀態下才執行。 好比有人會這麼作:

new Promise(function(resolve){
    reslove('ddd')
    resolve('ttt')
}).then(value=>{
    console.log(value)
})
複製代碼

爲了只打印一個值,咱們必需要在resolve函數作個判斷,只有pending的時候會

function Promise(executor:any) {

    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending;

    this.resovlecbs = [];
    this.rejectcbs = [];
    this.value;
    this.error;

    const resolve = (value:object)=>{
        
        setTimeout(()=>{
            if(this.status===pStatus.pending){ // 避免重複執行。
                this.value = value;
                this.resovlecbs.forEach((item:Function)=>{
                    item(value);
                })
                this.status = pStatus.fulled; // 狀態改變
            }
        },0)
    }

    const reject = (error:Error)=>{
        

        setTimeout(()=>{ // why
            if(this.status===pStatus.pending){ // 添加了判斷 避免重複執行
                this.error = error;
                this.status = pStatus.rejected; //狀態改變
                if(this.rejectcbs.length ===0){
                    throw this.error;
                }  else {
                    this.rejectcbs.forEach((item:Function)=>{
                        item(error);
                    })
                }
            }
        },0)
       // if(this.rejectcbs.length === 0 ) throw error;
    } 

    try {
        executor(resolve,reject);
    } catch (error) {
        reject(error);
    }
}
複製代碼

基本完美了,然而仍是一個Promise A+標準的處理問題。當resolve(value)的value是個Promise的話,如:

let p1 = new Promise(function(resolve,reject){
    resolve('test')
})

new Promise(function(resolve,reject){
    resolve(p1)
}).then(value=>{
    console.log(value) // 須要打印test。
})
複製代碼

目前咱們會直接吧p1這個實例直接返回給then的onFullfilled。繼續改造下。

export default function Promise(executor:any)  {

    if( !isFunc(executor) ){
        throw 'Promise2 傳遞的參數不爲functon!!!';
    }

    this.status = pStatus.pending;

    this.resovlecbs = [];
    this.rejectcbs = [];
    this.value;
    this.error;

    const resolve = (value:object)=>{

        if( value instanceof Promise) { // 這裏直接判斷
            return value['then'](resolve, reject);
        }
        
        setTimeout(()=>{
            if(this.status===pStatus.pending){
                this.value = value;
                this.resovlecbs.forEach((item:Function)=>{
                    item(value);
                })
                this.status = pStatus.fulled;
            }
        },0)
    }

    const reject = (error:Error)=>{

        setTimeout(()=>{ // 
            if(this.status===pStatus.pending){
                this.error = error;
                this.status = pStatus.rejected;
                if(this.rejectcbs.length ===0){
                    throw this.error;
                }  else {
                    this.rejectcbs.forEach((item:Function)=>{
                        item(error);
                    })
                }
            }
        },0)
    } 

    try {
        executor(resolve,reject);
    } catch (error) {
        reject(error);
    }
}
複製代碼

4.Promise的catch和finally函數。

catch函數和finally函數實際上是語法糖,咱們徹底能夠用then替代的。讀者大大們思考下。。

下面給出代碼:

Promise.prototype.catch = function(catchcb:Function) {
    return this.then(undefined, catchcb); // 本質是then
}


Promise.prototype.finally = function (callback) {
   return this.then((value)=>{ // 本質是then
        callback();
        return value;
   },callback);
}
複製代碼

因此下面這種寫法

p.then(onResolve,onReject).catch(onCatch).finally(onFinal);
複製代碼

實際上是等於

p.then(onResolve,onReject).then(undefined,onCatch).then(onFinal,onFinal);
複製代碼

5.Promise.resolve。

阮一峯給出了這個函數的四種處理方式。

  • 1.傳遞的是Promise,
  • 2.傳遞的是thenable的對象 如 { then:function(){} }
  • 3.傳遞是非thenbale的值
  • 4.什麼也沒傳。

須要注意的是Promise.resolve,傳遞出來的必定是promise。 筆者的寫法是

Promise.resolve = function(handler){
    if(  isObject(handler)  && 'constructor' in handler && handler.constructor=== this) { // handler 是 Promise
        return handler;
    } else if (isObject(handler) && isFunc(handler.then) ){ // thenable
        return new this(handler.then.bind(handler));
    }  else { // 非thenable
        return new this(function(resolve){
            resolve(handler);
        })
    }   
}
複製代碼

能夠看到若是是:

狀況1,則直接原封不動的返回。

狀況2則返回一個Promise,且把對象的then函數,做爲參數傳遞進入Promise。

狀況3 直接把handler resolve掉。

6.Promise.reject.

Promise.reject可不像resolve這麼麻煩。徹底把傳遞的值直接傳遞出來。

Promise.reject = function() {
    const args = Array.prototype.slice.call(arguments);
    return new this((resolve, reject) => reject(args.shift()));
}
複製代碼

7.Promise.all。

首先是用法:

const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return new Promise(function(resolve,reject){
    setTimeout(()=>{
        resolve(id);
    },id);
  })
});


Promise.all(promises).then(function (posts) {
  console.log(posts);
})
複製代碼

首先傳入的是數組。其次就是全部的數組,其次就是數組存在的Promise所有都執行完後纔會進入all的 then函數中。 因此須要一個標記記錄實時記錄全部已經完成的promise。

而後就是傳入的數組 可能有promise也有可能傳遞的並不是是Promise,因此須要hack。 區分是否存在then函數。

Promise.all = function(arr) {

    if( !isArray(arr) ){
        throw 'all函數 傳遞的參數不爲Array!!!';
    }

    let args = Array.prototype.slice.call(arr);
    let resArr = Array.call(null,Array(arr.length)).map(()=>null); // 記錄全部的結果
    let handlerNum = 0; // 處理標記

    return new this((resolve,reject)=>{
        for(let i = 0;i<args.length;i++){
            let ifunc = args[i];
            if(ifunc && isFunc(ifunc.then)  ) { //是否存在then函數。
                ifunc.then(value=>{
                    resArr[i] = value;
                    handlerNum ++; // 標記添加
                    if(handlerNum>=arr.length){ // 完全完成
                        resolve(resArr) // 完成後的數組
                    }
                },error=>{
                    reject(error);
                });
            } else { // 非thenable
                resArr[i] = ifunc;
                handlerNum ++; // 標記添加
                if(handlerNum>=arr.length){ // 完全完成
                    resolve(resArr) // 完成後的數組
                }
            }
        }
    });
}
複製代碼

8. Promise.race。

直接上代碼吧,大體就是跑的最快的會做爲結果傳回

Promise2.race = function(arr) {
    if( !isArray(arr) ){
        throw 'race函數 傳遞的參數不爲Array!!!';
    }

    let args = Array.prototype.slice.call(arr);
    let hasResolve = false;

    return new this((resolve,reject)=>{
        for(let i = 0;i<args.length;i++){
            let ifunc = args[i];
            if(ifunc && isFunc(ifunc.then)  ) {
                ifunc.then(value=>{
                    !hasResolve &&  resolve(value)
                },error=>{
                    !hasResolve && reject(error);
                });
            } else {
                hasResolve = true;
                !hasResolve && resolve(ifunc)
            }
        }
    })

}
複製代碼

源碼地址

分析同事的問題。

源代碼大體以下

let test = function() {
    return new Promise((resolve,reject)=>{
        reject(new Error('test'))
    })
}

Promise.resolve('new').then(res=>{
    test().then(res2=>{
        ...
    })
}).catch(err=>{
    // use err
    console.log(err)
})

複製代碼

遇到的問題是,最後的catch裏面拿不到err。

文中咱們已經說過,catch只是then的語法糖,而then的值的傳遞,是靠onFullfilled的return 和 onRejected的return 傳遞了。問題這是在then裏面缺乏了return。

總結:

  • resolve作的三件事情。

    1)記錄傳遞的值,準備傳遞。

    2)執行then的onFullfilled函數

    3)更換狀態。

  • then的執行是根據狀態做出不一樣的相應的。

  • catch和finally只是then的語法糖,finally並不是是最後執行的意思,而是必定會執行的意思。

  • Promise.resolve能夠快捷的建立一個Promise,他返回的必定是一個Promise。

想要表達的更清楚點,因此致使內容過長。。。。

有任何問題,請在評論區提問,我會盡快答覆。

相關文章
相關標籤/搜索