GraphQL LogoGraphQL

執行

經過驗證後,GraphQL 查詢會由 GraphQL 伺服器執行,並傳回一個結果,其結構反映了所要求查詢的形狀,通常為 JSON。

沒有類型系統,GraphQL 無法執行查詢,讓我們使用一個範例類型系統來說明如何執行查詢。這是這些文章範例中使用的相同類型系統的一部分

type Query {
human(id: ID!): Human
}
type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Starship {
name: String
}

為了說明執行查詢時會發生什麼情況,讓我們使用一個範例來說明。

你可以將 GraphQL 查詢中的每個欄位視為前一個類型的函式或方法,傳回下一個類型。事實上,這正是 GraphQL 的運作方式。每個類型上的每個欄位都由稱為解析器的函式支援,而這個函式是由 GraphQL 伺服器開發人員提供的。執行欄位時,會呼叫對應的解析器來產生下一個值。

如果欄位產生一個純量值,例如字串或數字,則執行就會完成。但是,如果欄位產生一個物件值,則查詢將包含另一個適用於該物件的欄位選取。這會持續進行,直到到達純量值。GraphQL 查詢總是會在純量值結束。

根欄位與解析器#

在每個 GraphQL 伺服器的最上層,有一個類型代表進入 GraphQL API 的所有可能進入點,它通常稱為類型或查詢類型。

在此範例中,我們的查詢類型提供了一個名為 human 的欄位,它接受引數 id。這個欄位的解析器函式可能會存取資料庫,然後建構並傳回一個 Human 物件。

Query: {
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}

這個範例是用 JavaScript 編寫的,不過 GraphQL 伺服器可以用 許多不同的語言 建立。解析器函式會收到四個參數

  • obj 前一個物件,對於根查詢類型的欄位來說,通常不用到。
  • args GraphQL 查詢中提供給欄位的參數。
  • context 提供給每個解析器的值,並包含重要的脈絡資訊,例如目前已登入的使用者,或對資料庫的存取權。
  • info 包含與目前查詢相關的欄位特定資訊以及架構詳細資料的值,另請參閱 類型 GraphQLResolveInfo 以取得更多詳細資料

非同步解析器#

讓我們仔細看看這個解析器函式中發生了什麼事。

human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}

context 用於提供對資料庫的存取權,資料庫用來載入使用者的資料,方法是使用 GraphQL 查詢中作為參數提供的 id。由於從資料庫載入資料是非同步作業,因此會傳回 Promise。在 JavaScript 中,Promise 用於處理非同步值,但許多語言中都有相同概念,通常稱為 未來工作延遲。當資料庫傳回時,我們可以建構並傳回新的 Human 物件。

請注意,雖然解析器函數需要知道承諾,但 GraphQL 查詢並不需要。它只是希望 human 欄位傳回一些東西,然後它可以詢問 name。在執行期間,GraphQL 會等待承諾、未來和任務完成後才繼續執行,並且會以最佳並行性執行。

瑣碎的解析器#

現在,Human 物件可用,GraphQL 執行可以繼續執行其上要求的欄位。

Human: {
name(obj, args, context, info) {
return obj.name
}
}

GraphQL 伺服器由類型系統提供支援,用於決定下一步要執行什麼。甚至在 human 欄位傳回任何內容之前,GraphQL 就知道下一步將解析 Human 類型的欄位,因為類型系統告訴它 human 欄位將傳回 Human

在此情況下,解析名稱非常簡單。會呼叫名稱解析器函數,而 obj 參數是從前一個欄位傳回的 new Human 物件。在此情況下,我們預期 Human 物件具有 name 屬性,我們可以直接讀取和傳回。

事實上,許多 GraphQL 函式庫會讓您省略如此簡單的解析器,並且只會假設如果未提供欄位的解析器,則應讀取和傳回同名的屬性。

純量強制轉換#

在解析 name 欄位時,可以同時解析 appearsInstarships 欄位。appearsIn 欄位也可以有瑣碎的解析器,但讓我們仔細看看

Human: {
appearsIn(obj) {
return obj.appearsIn // returns [ 4, 5, 6 ]
}
}

請注意,我們的類型系統宣稱 appearsIn 將傳回具有已知值的列舉值,但此函數傳回數字!的確,如果我們查看結果,我們將看到傳回適當的列舉值。發生什麼事了?

這是標量強制轉換的範例。類型系統知道要預期什麼,並且會將解析器函式傳回的值轉換成符合 API 契約的內容。在此情況下,我們的伺服器上可能定義了一個使用數字(例如 456)的列舉,但在 GraphQL 類型系統中將其表示為列舉值。

列出解析器#

我們已經看過一點,當欄位傳回一串具有上述 appearsIn 欄位的項目時會發生什麼事。它傳回一串列舉值,而且由於這是類型系統所預期的,因此串列中的每個項目都強制轉換為適當的列舉值。當解析 starships 欄位時會發生什麼事?

Human: {
starships(obj, args, context, info) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}

此欄位的解析器不僅傳回 Promise,它傳回一串 Promise。Human 物件有一串他們駕駛的 Starships 的 id,但我們需要載入所有這些 id 才能取得真正的 Starship 物件。

GraphQL 會同時等待所有這些 Promise 繼續進行,當取得一串物件時,它會再次同時繼續載入這些項目中每個項目的 name 欄位。

產生結果#

當每個欄位解析完畢後,結果值會放入一個鍵值對應中,其中欄位名稱(或別名)為鍵,解析值為值。這會從查詢的最底層欄位一直持續到根查詢類型上的原始欄位。這些會共同產生一個結構,反映原始查詢,然後可以將其傳送(通常以 JSON 格式)給請求的客戶端。

讓我們最後再看一次原始查詢,看看這些解析函式如何產生結果

繼續閱讀 →內省