前端面試送命題(二)-callback,promise,generator,async-await

前言javascript

本篇文章適合前端架構師,或者進階的前端開發人員;我在面試vmware前端架構師的時候,被問到關於callback,promise,generator,async-await的問題。前端

首先咱們回顧一下javascript異步的發展歷程。java

ES6 之前:node

  回調函數(callback);nodejs express 中經常使用,ajax中經常使用。ios

ES6:面試

  promise對象; nodejs最先有bluebird promise的雛形,axios中經常使用。
ajax

  generator函數;nodejs koa框架使用率很高。
express

ES7:axios

  async/await語法; 當前最經常使用的異步語法,nodejs koa2 徹底使用該語法。
promise

回調函數callback

回調函數實際就是一個參數;將一個函數當作參數傳到另外一個函數裏,當那個函數執行完後,再執行傳進去的這個函數;這個過程就叫作回調。

回調字面也好理解,就是先處理本體函數,再處理回調的函數,舉個例子,方便你們理解。

 

function A(callback){
    console.log("我是主體函數");
    callback();
}

function B(){
    console.log("我是回調函數");
}

A(B);
/*輸出結果
我是主體函數
我是回調函數
*/

 

上面的例子很好理解,首先執行主體函數A,打印結果:我是主題函數;而後執行回調函數callback 也就是B,打印結果:我是回調函數。

promise對象

promise 對象用於一個異步操做的最終完成(或最終失敗)及其結果的表示。

簡單地說就是處理一個異步請求。咱們常常會作些斷言,若是我贏了你就嫁給我,若是輸了我就嫁給你之類的斷言。這就是promise的中文含義:斷言,一個成功,一個失敗。

舉個例子,方便你們理解:

promise構造函數的參數是一個函數,咱們把它稱爲處理器函數,處理器函數接收兩個函數reslovereject做爲其參數,當異步操做順利執行則執行reslove函數, 當異步操做中發生異常時,則執行reject函數。經過resolve傳入得的值,能夠在then方法中獲取到,經過reject傳入的值能夠在chatch方法中獲取到。

​由於thencatch都返回一個相同的promise對象,因此能夠進行鏈式調用。

 

function readFileByPromise("a.txt"){
    //顯示返回一個promise對象
    return new Promise((resolve,reject)=>{
        fs.readFile(path,"utf8",function(err,data){
            if(err)
                reject(err);
            else
                resolve(data);
        })
    })
}
//書寫方式二
readFileByPromise("a.txt").then( data =>{
    //打印文件中的內容
    console.log(data);
}).catch( error =>{
    //拋出異常,
    console.log(error);
})

 

generator函數

ES6的新特性generator函數(面試的時候掛在這裏),中文譯爲生成器,在之前一個函數中的代碼要麼被調用,要麼不被調用,還不存在能暫停的狀況,generator讓代碼暫停成待執行,定義一個生成器很簡單,在函數名前加個*號,使用上也與普通函數有區別。

舉個例子,方便你們理解:

 

function *Calculate(a,b){
  let sum=a+b;
  console.log(sum);
  let sub=a-b;
  console.log(sub);
}

 

上面即是一個簡單的generator聲明例子。

generator函數不能直接調用,直接調用generator函數會返回一個對象,只有調用該對象的next()方法才能執行函數裏的代碼。

let gen=Calculate(2,7);

執行該函數:

gen.next();
/*打印
9
-5
*/

其實單獨介紹generator並無太大的價值,要配合key yield,才能真正發揮generator的價值。yield能將生Generator函數的代碼邏輯分割成多個部分,下面改寫上面的生成器函數。

function *Calculate(a,b){
  let sum=a+b;
  yield console.log(sum);
  let sub=a-b;
  yield console.log(sub);
}
let gen=Calculate(2,7);
gen.next();
/*輸出
9*/

能夠看到這段代碼執行到第一個yield處就中止了,若是要讓裏邊全部的代碼都執行完就得反覆調用next()方法

let gen=Calculate(2,7);
gen.next();
gen.next();
/*輸出
9
-5*/

在用一個例子,來講明generator函數與回調函數的區別:

回調函數:

fs.readFile("a.txt",(err,data)=>{
    if(!err){
        console.log(data);
        fs.readFile("b.txt",(err,data)=>{
            if(!err)
                console.log(data);
        })
    }
})

這是一個典型的回調嵌套,過多的回調嵌套形成代碼的可讀性和可維護性大大下降,造成了使人深惡痛絕的回調地獄,試想若是有一天讓你按順序讀取10個文件,那就得嵌套10層,再或者需求變動,讀取順序要變了先讀b.txt,再度a.txt那改來真的不要太爽。

generator函數:

function readFile(path) {
    fs.readFile(path,"utf8",function(err,data){
          it.next(data);
    })
}

function *main() {
    var result1 = yield readFile("a.txt");
    console.log(result1);

    var result2 = yield readFile("b.txt");
    console.log(result2);

    var result3 = yield readFile("c.txt");
    console.log(result3);
}

var it = main();
it.next(); 

generator函數的強大在於容許你經過一些實現細節來將異步過程隱藏起來,依然使代碼保持一個單線程、同步語法的代碼風格。這樣的語法使得咱們可以很天然的方式表達咱們程序的步驟/語句流程,而不須要同時去操做一些異步的語法格式。

async-await

async函數返回一個promise對象,若是在async函數中返回一個直接量,async會經過Promise.resolve封裝成Promise對象。
咱們能夠經過調用promise對象的then方法,獲取這個直接量。

async function test(){
    return "Hello World";
}

var result=test();
console.log(result);
//打印Promise { 'Hello World' }

 

那如過async函數不返回值,又會是怎麼樣呢?

async function test(){
   
}
var result=test();
console.log(result);
//打印Promise { undefined }

await會暫停當前async的執行,await會阻塞代碼的執行,直到await後的表達式處理完成,代碼才能繼續往下執行。
await後的表達式既能夠是一個Promise對象,也能夠是任何要等待的值。
若是await等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,而後獲得 resolve 的值,做爲 await 表達式的運算結果。


上邊你看到阻塞一詞,不要驚慌,async/await只是一種語法糖,代碼執行與多個callback嵌套調用沒有區別,本質並非同步代碼,它只是讓你思考代碼邏輯的時候可以以同步的思惟去思考,避開回調地獄,簡而言之-async/await是以同步的思惟去寫異步的代碼,因此async/await並不會影響node的併發數,你們能夠大膽的應用到項目中去!

若是它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。

舉個例子,方便你們理解:

function A() {
    return "Hello ";
}

async function B(){
    return "World";
}

async function C(){
    //等待一個字符串
    var s1=await A();
    //等待一個promise對象,await的返回值是promise對象resolve的值,也就是"World"
    var s2=await B();
    console.log(s1+s2);
}

C();
//打印"Hello World"
相關文章
相關標籤/搜索