nodejs筆記-非同步程式設計

mai-kuraki發表於2018-04-23

1.函數語言程式設計

1.1高階函式

函式引數只接受基本資料型別或者物件引用,返回值也是基本資料型別和物件引用。

//常規引數傳遞和返回
function foo(x) {
    return x;
}
複製程式碼

高階函式則是可以把函式作為引數和返回值的函式。

function foo(x) {
    return function() {
        return x
    }
}
複製程式碼
function foo(x, bar) {
    return bar(x);
}
複製程式碼

上面這個函式相同的foo函式但是傳入的bar引數不同則可以返回不同的結果,列如陣列的sort()方法,它是一個高階函式,接受一個引數方法作為排序運算。

var arr = [40, 80, 60, 5, 30, 77];
arr.sort(function(a, b) {
    return a - b;
});
//執行結果[5, 30, 40, 60, 77, 80];
複製程式碼

通過修改sort方法的引數可以決定不同的排序方法,這就是高階函式的靈活性。
結合Node的基本事件模組可以看到,事件處理方式正是基於高階函式的特性來完成的。在自定義事件中,通過為相同的事件註冊不同的回撥函式,可以很靈活的處理業務邏輯。

var emit = new events.EventEmitter();
emit.on('event', function() {
  //do something  
});
複製程式碼

1.2偏函式

偏函式指的是建立一個呼叫另外一部分(引數或者變數已經預置的函式)的函式的用法例如:

var toString = Object.prototype.toString;
var isString = function(obj) {
    return toString.call(obj) == '[object String]';
}
var isFunction = function(obj) {
    return toString.call(obj) == '[object Function]';
}
複製程式碼

這段程式碼用於判斷型別,通常會進行上述定義,程式碼存在相似性如果要判斷更多會定義更多的isXXX()方法,這樣就會出現冗餘程式碼。為了解決重複問題,引入一個新函式用於批量建立這樣的類似函式。

var isType = function (type) {
    return function(obj) {
        return toString.call(obj) == '[object '+ type +']';
    }
}
var isString = isType('String');
var isFunction = isType('Function');
複製程式碼

這種通過指定部分引數來產生一個新的定製函式的形式就是偏函式。

2.非同步程式設計優勢與難點

2.1非同步優勢

Node的最大特性是基於事件驅動的非阻塞I/O模型,這使得CPU和I/O不互相依賴,讓資源更好的利用,對於網路應用而言使得各個單點之間可以更有效的組織起來,這使得Node在雲端計算中廣受青睞。 由於事件迴圈模型要應對海量請求,所有請求作用在單執行緒上需要防止任何一個計算過多的消耗CPU時間片。建議計算對CPU的耗用不超過10ms,或將大量的計算分解成小量計算,通過setImmediate()進行排程。

2.2難點

1.異常處理

通常處理異常時使用 try/catch/final語句進行異常捕獲:

try {
    JSON.parse(str);
}catch(e) {
    console.log(e)
}
複製程式碼

但這對於非同步程式設計不一定適用。I/O實現非同步有兩個階段:提交請求和處理結果。這兩個階段中間有事件迴圈排程,彼此互不關聯,一步方法通常在第一個階段請求提交後立即返回,但是錯誤異常並不一定發生在這個階段,try/catch就不一定會發生作用了。

var async = function(callback) {
    process.nextTick(callback);
}

try {
    async(callback);
}catch(e) {
    
}
複製程式碼

呼叫async方法後callback會被存起來知道下一個事件迴圈才被執行,try/catch操作只能捕獲當前時間迴圈內的異常。對callback中的異常不起作用。
Node在處理異常上形成了一個約定,將異常作為回撥的第一個引數傳回,如果是空值,表明沒有異常丟擲:

async(function(err, res)) {
    
});
複製程式碼

在自行編寫的非同步方法上也需要去遵循這樣的原則: 1.必須執行呼叫者傳入的回撥函式; 2.正確的傳回異常供呼叫者判斷;

var async = function(callback) {
    process.nextTick(function() {
        var res = 'something';
        if(error) {
            return callback(error);
        }else {
            return callback(null, res);
        }
    })
}
複製程式碼

在非同步程式設計中,另一個容易犯的錯誤是對使用者傳遞的callback進行異常捕獲,

try {
    req.body = JSON.parse(buf, options.reviver);
    callback();
}catch(e) {
    err.body = buf;
    err.status = 400;
    callback(e);
}
複製程式碼

如果JSON.parse出現錯誤程式碼將進入catch部分這樣回撥函式callback將被執行兩次,正確的做法應該是

try {
    req.body = JSON.parse(buf, options.reviver);
}catch(e) {
    err.body = buf;
    err.status = 400;
    return callback(e);
}
callback();
複製程式碼
2.函式巢狀過深

Node中事物中會出現多個非同步呼叫的場景,列如遍歷目錄:

fs.readdir('path', function(err, files) {
    files.forEach(function(fileName, index) {
        fs.readFile(fileName, 'utf8', function(err, file) {
            //TODO
        })
    })
});
複製程式碼

上述操作由於兩次操作存在依賴關係,函式巢狀行為情有可原,但是在某些場景列如渲染網頁:通常需要資料、模版、資原始檔,這三個操作互不依賴但是最終結果又是三者不可缺一,如果採用預設非同步呼叫會是這樣:

fs.readFile('path', 'utf8', function(err, templete) {
    db.query(sql, function(err, data) {
        l10n.get(function(err, res) {
            //TODO
        })   
    })
})
複製程式碼

這樣導致了程式碼巢狀過深,不易讀且不好維護。

3.阻塞程式碼

javascript沒有sleep()這樣讓執行緒沉睡的功能,只有setInterval()和setTimeout()這兩個函式,但是這兩個函式不能阻塞後面的程式碼執行。

4.多執行緒程式設計

說到javascript時候,通常談的是單執行緒上執行。隨著業務複雜,對於多核CPU的利用要求也越來越高。瀏覽器中提出了Web Workers。可以更好的利用多核CPU為大量計算服務。前端Web Workers也是利用訊息機制合理的使用多核CPU的理想模型。

nodejs筆記-非同步程式設計
Web Workers的工作示意圖
Node借鑑了這個模式,child_process是其基礎API,cluster模組是更深層次的應用。

5.非同步轉同步

Node提供了絕大部分的非同步API和少量的同步API,Node中試圖同步程式設計,但並不能得到原生支援,需要藉助庫或者編譯手段實現。

3.非同步程式設計解決方案

目前,非同步程式設計主要解決方案有三種:

  1. 事件釋出/訂閱模式
  2. Promise/Deferred模式
  3. 流程控制庫

3.1事件釋出/訂閱模式

Node自身提供的events模組是釋出/訂閱的一個簡單實現,Node中部分模組都繼承自它。它具有addListener/on()、 once()、 removeListener()、 removeAllListener()、 emit() 等基礎方法。

//訂閱
emitter.on('event', function(msg) {
    console.log(msg)
});
emitter.emit('event', 'this is msg');
複製程式碼

Node對事件釋出/訂閱的機制做了一些額外處理:

  1. 如果對一個事件新增超過10個偵聽器,將會得到一條警告,設計者認為太多的偵聽器可能會導致記憶體洩漏,呼叫emitter.setMaxListener(0);可以去掉這個限制。
  2. 為了異常處理,EventEmitter物件對error事件進行了特殊對待。如果執行期間的錯誤出發了error事件,EventEmitter會檢測是否對error事件新增過偵聽器,如果加了,這個錯誤將交給偵聽器處理,否則將作為異常丟擲。如果外部沒有捕獲異常,將會引起執行緒退出。
1.繼承events模組

實現一個繼承EventEmitter的類:

var events = require('events');
function Stream() {
    events.EventEmitter.call(this);
}
util.inherits(stream, events.EventEmitter);
複製程式碼

Node在util模組封裝了繼承方法。

2.利用事件列隊結局雪崩問題

在事件訂閱/釋出模式中,通常有一個once()方法,通過它新增的偵聽器只能執行一次,執行後將被移除。
在計算機中,快取由於放在記憶體中,訪問書的快,用於加速資料訪問,讓絕大多數請求不必重複去做一些低效的資料讀取。所謂的雪崩問題,就是高訪問量、大併發量的情況下快取失效的情況,此時大量的請求同時湧入資料庫中,資料庫無法承受大量查詢請求進而影響網站整體響應速度。

//查詢資料庫
var select = function(callback) {
    db.select(sql, function(err, res) {
        callback(res);
    })
}
複製程式碼

如果站點剛啟動快取中還沒有資料,如果訪問量巨大,同一句sql會被執行多次反覆查詢資料庫,將會影響效能。改進方案:加一個狀態鎖

var status = 'ready';
var select = function(callback) {
    if(status === 'ready') {
        status = 'pending';
        db.select(sql, function(err, res) {
            status = 'ready';
            callback(res);
        })
    }
}
複製程式碼

但是在這種情況下連續多次呼叫只有第一次呼叫是生效的,後續呼叫是沒有資料服務的,這個時候可以引入事件列隊:

var proxy = new events.EventEmitter();
var status = 'ready';
var select = function(callback) {
    proxy.once('selected', callback);
    if(status == 'ready') {
        status = 'pending';
        db.select(sql, function(err, res) {
            proxy.emit('selected')
            status = 'ready';
        })
    }
}
複製程式碼

利用once()方法,將所有請求的回撥壓入事件列隊,利用其執行一次就好將監視器移除的特點,保證一次回撥只會被執行一次。

3.多非同步之間的協作方案

以上面提到的渲染網頁(模版讀取、資料讀取、本地資源讀取)為例:

var count = 0;
var res = {};
var done = function(key, val) {
    res[key] = val;
    count ++;
    if(count === 3) {
        render(res);
    }
};
fs.readFile(template_path, 'utf8', function(err, tp) {
    done('tp', tp);
}); 
db.query(sql, (err, data) {
    done('data', data);
});
l10n.get(function(err, res) {
    done('res', res);
});
複製程式碼

通常用於檢測次數的變數叫做'哨兵變數'。利用偏函式來處理哨兵變數和第三方函式的關係:

var after = function(times, callback) {
    var count = 0,res = {};
    return function(key, val) {
        res[key] = val;
        count ++;
        if(count == times) {
            callback(res);
        }
    }
}

var emitter = new events.EventEmitter();
var done = after(times, render);

emitter.on('done', done);
emitter.on('done', other);

fs.readFile(template_path, 'utf8', function(err, tp) {
    emitter.emit('done', 'tp', tp);
}); 
db.query(sql, (err, data) {
    emitter.emit('done', 'data', data);
});
l10n.get(function(err, res) {
    emitter.emit('done', 'res', res);
});
複製程式碼
4.EventProxy

撲靈寫的EventProxy模組,是對事件訂閱/釋出模式的擴充

var proxy = new EventProxy();
proxy.all('tp', 'data', 'res', function(tp, data, res) {
    //TODO
});
fs.readFile(template_path, 'utf8', function(err, tp) {
    proxy.emit('tp', tp);
}); 
db.query(sql, (err, data) {
    proxy.emit('data', data);
});
l10n.get(function(err, res) {
    proxy.emit('res', res);
});
複製程式碼

EventProxy提供了all()方法來訂閱多個事件,所有事件觸發後偵聽器才會被觸發。另一個tail()方法在滿足條件時只需一次後,如果組合事件中的某個事件再次被觸發,偵聽器會用最新的資料繼續只需。
after()方法:實現事件在執行多少次後執行偵聽器的單一事件組合訂閱方式:

//執行10次data事件後觸發偵聽器
var proxy = new EventProxy();
proxy.after('data', 10, function(datas) {
    //TODO
})
複製程式碼

EventProxy原理
EventProxy來源自Backbone的事件模組,它在每個非all的事件觸發時都會觸發一次all事件

trigger: functuon(eventName) {
    var list, calls, ev, callback, args;
    var both = 2;
    if(!(calls = this._callbacks)) return;
    while (both--) {
        ev = both?eventName:'all';
        if(list = calls[ev]) {
            for(var i = 0, l = list.length; i < 1; i ++) {
                if(!(callback = list[i])) {
                    list.splice(i, i);
                    i --;
                    l --;
                }else {
                    args = both? Array.prototype.slice.call(arguments, 1):argument;
                    callback[0].apply(callback[1] || this, args);
                }
            }
        }
    }
    return this;
}
複製程式碼

EventProxy則是將all當做一個事件流的攔截層,在其中注入一些業務來處理單一事件無法解決的非同步處理問題。

5.EventProxy異常處理

EventProxy提供了fail()和done()兩個例項方法來優化異常處理。

var proxy = new EventProxy();
proxy.all('tp', 'data', function(tp, data, res) {
    //TODO
});
proxy.fail(function(err) {
    //錯誤處理
})
fs.readFile(template_path, 'utf8', proxy.done('tp')); 
db.query(sql, proxy.done('data'));

proxy.done('tp')等價於
function(err, data) {
    if(err) {
        return proxy.emit('error', err)
    }
    proxy.emit('tp', data)
}
複製程式碼

3.2.Promise/Deferred模式

使用事件的方式時,執行的流程被預先設定。Promise/Deferred模式先執行非同步呼叫,延遲傳遞處理方式。

//普通jquery Ajax呼叫
$.get('api',{
    success: onSuccess,
    error: onError,
    complete: onComplete
})
複製程式碼

需要提前預設對應的回撥函式

//Promise/Deferred模式 jquery Ajax呼叫
$.get('api')
    .success(onSuccess)
    .error(onError)
    .complete(onComplete);
複製程式碼

Promise/Deferred模式即使不傳入回撥函式也能執行,傳統方法一個事件只能傳入一個回撥函式,而Deferred物件可以對事件加入任意業務處理邏輯。

$.get('api')
    .success(onSuccess1)
    .success(onSuccess2);
複製程式碼

CommonJS抽象出了 Promises/A,Promises/B,Promises/D這樣的典型非同步Promise/Deferred模型。

1.Promises/A

Promises/A提議對單個非同步操作做出這樣的定義:

  1. Promise操作只會處在3種狀態的一種:未完成狀態、完成狀態、失敗狀態。
  2. Promise的狀態只會出現從未完成向完成或者失敗狀態轉換,不能逆向。完成和失敗不能互相轉換。
  3. Promise狀態一旦轉換將不能被更改。

Promise狀態轉換示意圖
Promise狀態轉換示意圖

Promises/A API定義比較簡單。一個Promise物件只要具備then()方法即可。對應then()的要求:

  1. 接受完成、錯誤的回撥方法。操作完成或錯誤時,將會呼叫對應方法。
  2. 可選的支援progress事件回撥作為第三個方法。
  3. then()方法只接受function物件,其他的會被忽略。
  4. then()方法繼續返回Promise物件,實現鏈式呼叫。

then()方法定義:

then(fulfilledHandler, errorHandler, progressHandler)  
複製程式碼

Promises/A演示:

var Promise = function() {
    EventEmitter.call(this);
};
util.inherit(Promise, EventEmitter);

Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
    if(typeof fulfilledHandler === 'function') {
        this.once('success', fulfilledHandler)
    }
    if(typeof errorHandler === 'function') {
        this.once('error', errorHandler)
    }
    if(typeof progressHandler === 'function') {
        this.on('progress', progressHandler)
    }
    return this;
}
複製程式碼

then()方法將回撥函式存放起來,為了完成整改流程,還需要觸發執行這些函式的地方,實現這些功能的物件被稱為Deferred,即延遲物件:

var Deferred = function() {
    this.state = 'unfulfilled';
    this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj) {
    this.state = 'fulfilled';
    this.promise.emit('success', obj);
}
Deferred.prototype.reject = function(obj) {
    this.state = 'faild';
    this.promise.emit('error', obj);
}
Deferred.prototype.progress = function(obj) {
    this.promise.emit('progress', obj);
}
複製程式碼

狀態與方法對應關係
狀態與方法對應關係
利用Promises/A模式,對一個典型的響應物件進行封裝:

res.setEncoding('utf8');
res.on('data', function(chunk) {
    //成功
    console.log(chunk)
});
res.on('end', function() {
    //失敗
})
res.on('error', function(err) {
    //progress
}) 
//通過改造
var promisify = function(res) {
    var deferred = new Deferred();
    var res = '';
    res.on('data', function(chunk) {
        res += chunk;
        deferred.progress(chunk);
    })
    res.on('end', function() {
        promise.resovle(res);
    })
    res.on('error',function(err) {
        promise.reject(err)
    })
    return deferred.promise;
}
//簡便寫法
promisify(res).then(function() {
    //成功
},function(err) {
    //失敗
}, function(chunk) {
    //progress
})
複製程式碼

從上面程式碼可以看出,Deferred主要用於內部,用於維護非同步模型的狀態;Promise則作用於外部,通過then()方法暴露給外部新增自定義邏輯。

nodejs筆記-非同步程式設計
Promise和Deferred整體關係示意圖
於事件釋出/訂閱相比Promise/Deferred模式API介面更加簡潔,它將不變的部分封裝在Deferred中,將可變的部分交個Promise。

Q模組...

2.Promise中的多非同步協作

簡單原型實現:

Deferred.prototype.all = function(promises) {
    var count = promises.length;
    var that = this;
    var res = [];
    promises.forEach(function(promise, i) {
        promise.then(function(data) {
            count --;
            res[i] = data;
            if(count === 0) {
                that.resolve(res);
            }
        }, function(err) {
            that.reject(err);
        })
        return this.promise;
    })
}
複製程式碼

通過all()方法抽象多個非同步操作。只有所有非同步操作成功,這個非同步操作才算成功,其中有一個失敗,整個非同步操作就失敗。
(實際使用推薦使用when,Q模組,是對完整的Promise提議的實現)

3.Promise的進階

現有一組純非同步的API,為完成一件串聯事程式碼如下:

obj.api1(function(val1) {
    obj.api2(val1, function(val2) {
        obj.api3(val2, function(val3) {
            obj.api4(val3, function(val4) {
                callback(val4);
            })
        })
    })
})
複製程式碼

使用普通函式將上面程式碼展開:

var handler1 = function(val1) {
    obj.api2(val1, handler2);
}
var handler2 = function(val2) {
    obj.api3(val2, handler3);
}
var handler3 = function(val3) {
    obj.api4(val3, handler4);
}
var handler41 = function(val4) {
    callback(val4)
}
obj.api1(handler1);
複製程式碼

使用事件機制

var emitter = new EventEmitter();

emitter.on('step1', function() {
    obj.api1(function(val1) {
        emitter.emit('step2', val1);
    })
})
emitter.on('step2', function(val1) {
    obj.api2(val1, function(val2) {
        emitter.emit('step3', val2);
    })
})
emitter.on('step3', function(val2) {
    obj.api3(val2, function(val3) {
        emitter.emit('step42', val3);
    })
})
emitter.on('step4', function(val3) {
    obj.api4(val3, function(val4) {
        callback(val4);
    })
})
emitter.emit('step1');
複製程式碼

使用事件後程式碼量明顯增加,需要一種更好的方式。
支援序列執行的Promise
理想的方法---鏈式呼叫:

promise()
    .then(obj.api1)
    .then(obj.api2)
    .then(obj.api3)
    .then(obj.api4)
    .then(function(val4) {
        
    },function(err) {
        
    }).done();
複製程式碼

通過改造程式碼以實現鏈式呼叫:

var Deferred = function() {
    this.promise = new Promise();
}
//完成狀態
Deferred.prototype.resolve = function(obj) {
    var promise = this.promise;
    var handler;
    while((handler = promise.queue.shift())) {
        if(handler && handler.fulfilled) {
            var ret = handler.fulfilled(obj);
            if(ret && ret.isPromise) {
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
}
//失敗狀態
Deferred.prototype.reject = function(err) {
    var promise = this.promise;
    var handler;
    while((handler = promise.queue.shift())) {
        if(handler && handler.error) {
            var ret = handler.error(err);
            if(ret && ret.isPromise) {
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
}
//生成回撥函式
Deferred.prototype.callback = function() {
    var that = this;
    return function(err, file) {
        if(err) {
            return that.reject(err);
        }else {
            that.resolve(file);
        }
    }
}

var Promise = function() {
    this.queue = [];
    this.isPromise = true;
}

Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
    var handler = {};
    if(typeof fulfilledHandler === 'function') {
        handler.fulfilled = fulfilledHandler;
    }
    if(typeof errorHandler === 'function') {
        handler.errorHandler = errorHandler;
    }
    this.queue.push(handler);
    return this;
}
複製程式碼

以兩次檔案讀取為例,假設讀取第二個檔案是依賴第一個檔案中的內容:

var readFile1 = function(file, encoding) {
    var deferred = new Deferred();
    fs.readFile(file, encoding, deferred.callback());
    return deferred.promise;
}
var readFile2 = function(file, encoding) {
    var deferred = new Deferred();
    fs.readFile(file, encoding, deferred.callback());
    return deferred.promise;
}
readFile1('file1.txt', 'utf8').then(function(file1) {
    return readFile2(file1.trim(), 'utf8');
}).then(function(file2) {
    //file2
})
複製程式碼

要讓Promise支援鏈式執行,主要通過兩個步驟。

  1. 將所有的回撥都存到佇列中。
  2. Promise完成時,逐個執行回撥,一旦檢測到返回了新的Promise物件,就停止執行,然後將當前Deferred物件的promise引用改為新的Promise物件,並將列隊中餘下的回撥轉給它。
    將API Promise化 為了更好體驗的API,需要較多的準備工作。批量將方法Promise化:
//smooth(fs.readFile)
var smooth = function(method) {
    return function() {
        var deferred = new Deferred();
        var ags = Array.prototype.slice.call(argument, 1);
        args.push(deferred.callback());
        method.apply(null, args);
        return deferred.promise;
    }
}
複製程式碼

上面的兩次讀取檔案可以簡化為:

var readFile = smooth(fs.readFile);
readFile('file1.txt', 'utf8').then(function(file1) {
    return readFile(file1.trim(), 'utf8');
}).then(function(file2) {
    
})
複製程式碼

3.3.ES6 Generator

書中沒有提到這種模式,下面補充一下。
Generator函式是ES6提供的一個非同步解決方案。最大的特點是可以暫停執行。 Generator函式和普通的函式區別有兩個, 1:function和函式名之間有一個*號, 2:函式體內部使用了yield表示式
呼叫 Generator 函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件(Iterator Object)需要呼叫遍歷器的next()方法才能使函式繼續執行,直到遇到yield方法再次暫停執行。

function* gen() {
    var a = 10;
    console.log(a);
    yield a ++;
    console.log(a);
}
複製程式碼

上面是一個Generator函式,執行:

var g = gen();
複製程式碼

並麼有列印出a的值,執行gen()後只是得到了一個遍歷器物件。

g.next();
//10
複製程式碼

執行遍歷器的next()方法後輸出了10。

g.next();
//11
複製程式碼

再次執行next(),yield後的語句被執行輸出10。
遇到yield表示式,就暫停執行後面的操作,並將緊跟在yield後面的那個表示式的值,作為返回的物件的value屬性值

使用ES6的Promise和Generator解決惡魔金字塔
function *readFileStep(path1) {
    let path2 = yield new Promise((resovle, reject) => {
        fs.readFile(path1, 'utf8', (err, data) => {
            resovle(data);
        });
    });
    let path3 = yield new Promise((resovle, reject) => {
        fs.readFile(path2, 'utf8', (err, data) => {
            resovle(data);
        });
    });
    return new Promise((resovle, reject) => {
        fs.readFile(path3, 'utf8', (err, data) => {
            resovle(data);
        });
    });
}
function run(it) {
    function go(result) {
        if (result.done) {
            return result.value;
        }
        return result.value.then(function(value) {
            return go(it.next(value));
        });
    }
    return go(it.next());
};

run(readFileStep('./file1.txt')).then((data) => {
    console.log(data)
});
複製程式碼
  1. 定義一個Generator函式readFileStep,內含三個非同步的讀取檔案yield函式,並且每個函式返回一個Promise。
  2. 實現一個執行器,獲取每次非同步執行返回的Promise物件的結果,如果結果返回後且遍歷器不是完成狀態就繼續執行next()方法,直到完成返回最終的Promsie物件。
  3. 將Generator函式放入執行器中執行,得到最終的Promsie物件進行操作。

3.4.ES7 Async/Await

async函式是對Generator函式的改進,在ES7中出現。Generator函式需要依靠執行器才能執行,async函式自帶執行器執行方法與常規函式一樣。
與Generator函式一樣在非同步操作的時候async函式返回一個Promise物件,使用then()方法進行後續處理。
使用async實現Generator函式中的樣例:

async function readFileStep(path1) {
    let path2 = await new Promise((resovle, reject) => {
        fs.readFile(path1, 'utf8', (err, data) => {
            resovle(data);
        });
    });
    let path3 = await new Promise((resovle, reject) => {
        fs.readFile(path2, 'utf8', (err, data) => {
            resovle(data);
        });
    });
    return new Promise((resovle, reject) => {
        fs.readFile(path3, 'utf8', (err, data) => {
            resovle(data);
        });
    });
}

readFileStep('./file1.txt').then((data) => {
    console.log(data)
});
複製程式碼

只需要像普通函式一樣執行readFileStep即可得到最終結果的Promise物件。

相關文章