傳統的解決代碼單線程執行的方案是回調函數和事件
。這是個解決問題的方案,可是會形成回調地獄。html
異步編程是優化代碼邏輯提升代碼易讀性的關鍵。web
目前通用的異步編程方法有三種:npm
這三種方法我都常常在用,可是對它們的原理卻只知其一;不知其二。因而想炒個冷飯從頭至尾理一遍,梳理一下它們之間的關係。編程
Promise對象是一個構造函數,用來生成Promise實例。api
Promise對象表明一個異步操做,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。數組
Promise函數的兩個參數分別是resolve
和reject
。它們是Promise中定義的兩個函數,在運行自定義函數時返回。promise
resolve
函數將Promise對象的狀態從 pending
變爲resolved
,reject
將Promise對象的狀態從 pending
變爲rejected
bash
Promise的原型鏈上定義了then方法,提供兩個回調函數分別捕獲resolve、reject返回的值。異步
方法 | 描述 |
---|---|
Promise.resolve(promise); | 返回 promise(僅當 promise.constructor == Promise 時) |
Promise.resolve(thenable); | 從 thenable 中生成一個新 promise。thenable 是具備 then() 方法的相似於 promise 的對象。 |
Promise.resolve(obj); | 在此狀況下,生成一個 promise 並在執行時返回 obj。 |
Promise.reject(obj); | 生成一個 promise 並在拒絕時返回 obj。爲保持一致和調試之目的(例如堆疊追蹤), obj 應爲 instanceof Error。 |
Promise.all(array); | 生成一個 promise,該 promise 在數組中各項執行時執行,在任意一項拒絕時拒絕。 |
Promise.race(array); | 生成一個 Promise,該 Promise 在任意項執行時執行,或在任意項拒絕時拒絕,以最早發生的爲準。 |
sample 1async
let p1 = new Promise((resolve,reject)=>{
console.log('hello')
setTimeout(function () {
reject('1212')
},1000)
})
p1.then(data=> {
console.log('success'+data)
},err=>{
console.log('err'+err)
})
p1.then(data=> {
console.log('success'+data)
},err=>{
console.log('err'+err)
})
複製代碼
terminal:
hello
err1212
err1212
複製代碼
sample 1 中新建了一個Promise實例,定時1S後使用reject方法,將Promise實例的狀態從pending變成rejected,觸發then的err捕捉回調函數。
在sample 1 中調用then方法,並不會立刻執行回調。是等待實例中狀態改變後纔會執行。這一點和發佈訂閱
模式很相似。
sample 2
let fs = require('fs')
let event = {
arr:[],
result:[],
on(fn){
this.arr.push(fn)
},
emit(data){
this.result.push(data)
this.arr.forEach(fn=>fn(this.result))
}
}
event.on(function (data) {
if(data.length === 2){
console.log(data)
}
})
fs.readFile('1.txt','utf8',function (err,data) {
event.emit(data)
})
fs.readFile('2.txt','utf8',function (err,data) {
event.emit(data)
})
複製代碼
smaple2 中將結果data放入暫存數組中,在執行接聽函數的時候返回。
經過以前的例子和對發佈訂閱模式的理解,咱們能夠大概寫出Promise實例的基本功能:
code 1:
function Promise(executor) {
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResovedCallbacks = []
self.onRejectedCallbacks = []
function resolve(data) {
if(self.status === 'pending'){
self.value = data
self.status = 'resolved'
self.onResovedCallbacks.forEach(fn=>fn())
}
}
function reject(reason) {
if(self.status === 'pending') {
self.reason = reason
self.status = 'reject'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
//若是函數執行時發生異常
try{
executor(resolve,reject)
}catch (e){
reject(e)
}
}
Promise.prototype.then = function (onFulfilled,onRejected) {
let self = this
if(self.status === 'pending'){
self.onResovedCallbacks.push(()=>{
onFulfilled(self.value)
})
self.onRejectedCallbacks.push(()=>{
onRejected(self.reason)
})
}else if(self.status === 'resolved'){
onFulfilled(self.value)
}else if(self.status === 'reject'){
onRejected(self.reason)
}
}
module.exports = Promise
複製代碼
let fs = require('fs')
function read(filePath,encoding) {
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=> {
if(err) reject(err)
resolve(data)
})
})
}
read('1.txt','utf8').then(
f1=>read(f1,'utf8') // 1
).then(
data=> console.log('resolved:',comments)
err=> console.log('rejected: ',err)
)
複製代碼
......
read('1.txt','utf8').then(
f1=>read(f1,'utf8')
).then(
return 123 //2
).then(
data=> console.log('resolved:',comments)
err=> console.log('rejected: ',err)
)
複製代碼
......
read('1.txt','utf8').then(
f1=>read(f1,'utf8')
).then(
return 123
).then(
throw new Error('出錯') //3
).then(
data=> console.log('resolved:',comments)
err=> console.log('rejected: ',err)
)
複製代碼
......
read('1.txt','utf8').then(
f1=>read(f1,'utf8')
).then(
return 123
).then(
throw new Error('出錯')
).then() //6
.then(
data=> console.log('resolved:',comments)
err=> console.log('rejected: ',err)
)
複製代碼
這些用法中最重要的是promise的then鏈式調用。 能夠大體猜到,舊Promise的then方法返回的是一個新的Promise對象。
參考Promises/A+規範,能夠完善手寫的Promise源碼使其支持promise的靜態方法和調用規則。
code 2:
function Promise(executor) {
let self = this
self.value = undefined
self.reason = undefined
self.status = 'pending'
self.onResovedCallbacks = []
self.onRejectedCallbacks = []
function resolve(value) {
if (self.status === 'pending') {
self.value = value
self.status = 'resolved'
self.onResovedCallbacks.forEach(fn=>fn())
}
}
function reject(reason) {
if (self.status === 'pending') {
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(fn=>fn())
}
}
//若是函數執行時發生異常
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
function resolvePromise(promise2, x, resolve, reject) {
//If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise2 === x) {
return reject(new TypeError('chaining cycle'))
}
let called
//2.3.3.Otherwise, if x is an object or function,
if (x !== null && (typeof x == 'object' || typeof x === 'function')) {
try {
let then = x.then
//2.3.3.3.If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:
//2.3.3.3.3.If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
if (typeof then === 'function') {
then.call(x, y=> {
if (called) return;
called = true;
//遞歸直到解析成普通值爲止
//2.3.3.1.If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
resolvePromise(promise2, y, resolve, reject)
}, err=> {
if (called) return;
called = true;
reject(err)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return;
called = true;
//2.3.3.3.If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
reject(e)
}
} else {
//If x is not an object or function, fulfill promise with x.
resolve(x)
}
}
//then調用的時候 都是異步調用 (原生的then的成功或者失敗 是一個微任務)
Promise.prototype.then = function (onFulfilled, onRejected) {
//成功和失敗的函數 是可選參數
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val=>val;
onRejected = typeof onRejected === 'function' ? onRejected : (e)=> {throw e};
let self = this
let promise2;
promise2 = new Promise((resolve, reject)=> {
if (self.status === 'resolved') {
setTimeout(()=> {
try {
let x = onFulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (self.status === 'rejected') {
setTimeout(()=> {
try {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (self.status === 'pending') {
self.onResovedCallbacks.push(()=> {
setTimeout(()=> {
try {
let x = onFulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
//當執行成功回調的時候,可能會出現異常,那就用這個異常做爲promise2的錯誤結果
reject(e)
}
}, 0)
})
self.onRejectedCallbacks.push(()=> {
setTimeout(()=> {
try {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
//setTimeout (規範要求)
Promise.reject = function (reason) {
return new Promise((resolve,reject)=>{
reject(reason)
})
}
Promise.resolve = function (value) {
return new Promise((resolve,reject)=>{
resolve(value)
})
}
Promise.prototype.catch = function (onReject) {
return this.then(null,onReject)
}
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject)=> {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd;
}
module.exports = Promise
複製代碼
return p2 = new Promise()
複製代碼
2.增長resolvePromise方法,處理舊Promise的回調函數的結果x,根據x的類型,分別調用新promise對象的resolve/reject方法。
1: NodeJS 中的 fs.readFile 方法的基本使用方式
const fs = require('fs'),path = require('path');
fs.readFile(path.join(__dirname, '1.txt'), 'utf-8', (err, data) => {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
複製代碼
2:使用Promise封裝
let fs = require('fs')
function read(filePath, encoding) {
return new Promise((resolve, reject)=> {
fs.readFile(filePath, encoding, (err, data)=> {
if (err) reject(err)
resolve(data)
})
})
}
read('1.txt', 'utf8').then( data=> data)
複製代碼
把fs.readFile方法用Promise封裝一下就能使用Promise api。可是每次手動封裝比較麻煩,bluebird能夠幫咱們簡化這個步驟。
3:在 NodeJS 環境中,經過 const bluebird = require('bluebird') 就能夠開始使用 Bluebird 提供的 Promise 對象。
Promise.promisify 將單個方法轉換成Promise對象。
const bluebird = require('bluebird')
let read = bluebird.promisify(fs.readFile)
read('1.txt', 'utf-8').then(data=> {
console.log('data promisify', data)
})
複製代碼
使用bluebird.promisify
方法,就能將fs.readFile直接封裝成一個promise對象,它的原理很簡單,return new Promise 是它的核心:
function promisify(fn) {
return function () {
return new Promise((resolve, reject)=> {
fn(...arguments, function (err, data) {
if (err) reject(err)
resolve(data)
})
})
}
}
複製代碼
4.使用 Promise.promisifyAll 把一個對象的全部方法都自動轉換成使用 Promise。
const bluebird = require('bluebird'),
fs = require('fs'),
path = require('path');
Promise.promisifyAll(fs);
fs.readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8')
.then(data => console.log(data))
.catch(err => console.error(err));
複製代碼
promisifyAll核心是遍歷對象,生成些新建立方法的名稱在已有方法的名稱後加上"Async"後綴。
function promisifyAll(obj) {
Object.keys(obj).forEach(key=>{
if(typeof obj[key] === 'function'){
obj[key+'Async'] = promisify(obj[key])
}
})
}
複製代碼
generator函數最大的特色是能夠用yield
暫停執行,爲了區別普通函數在函數名前加*號。
function *say() {
let a = yield "test1"
let b = yield "test2"
}
let it = say();
console.log(1, it.next()) //1 { value: 'test1', done: false }
console.log(2, it.next()) //2 { value: 'test2', done: false }
console.log(3, it.next()) //3 { value: undefined, done: true }
複製代碼
執行say()方法返回的是指針對象,不會返回函數執行結果。it 就是iterator 迭代器
須要調用指針對象的next()方法,讓函數指針不斷移動並返回一個對象。({value:xxx,done:xxx})
value是yield後面的值,done表示函數是否執行完成。
咱們能夠用generator函數實現結果的產出,可是也須要它支持輸入。
generator函數的運行順序以下:
使用it.next()執行函數,結果並不會返回給定義的變量a。next方法能夠接受參數,這是向 Generator 函數體內輸入數據。 第二個next的時候傳入參數,就能被變量a接收到。
terminal 返回:
1 { value: 'test1', done: false }
aaa
2 { value: 'test2', done: false }
bbb
3 { value: undefined, done: true }
複製代碼
4.2 使用
example:使用generator異步執行函數,使函數的返回做爲下一個函數的入參執行。
let bluebird = require('bluebird')
let fs = require('fs')
let read = bluebird.promisify(fs.readFile)
function *r() {
let r1 = yield read('1.txt', 'utf-8')
console.log('r1',r1); // r1 2.txt
let r2 = yield read(r1, 'utf-8')
console.log('r2',r2); // r2 3.txt
let r3 = yield read(r2, 'utf-8')
console.log('r3',r3); // r3 hello
return r3
}
複製代碼
拿讀取文件的例子:使用bluebird將fs.readFile變成promise對象,將讀取到的文件內容做爲入參傳入下一個要執行的函數。
忽然發現,要拿到結果會是個複雜的過程,但仍是硬着頭皮下下去:
const it_r = r()
it_r.next().value.then(d1=>{
return it_r.next(d1).value
}).then(d2=>{
return it_r.next(d2).value
}).then(d3=>{
return it_r.next(d3).value
}).then(data=>{
console.log(data) // hello
})
複製代碼
it.next().value 返回的是一個promise,使用then方法,拿到它成功回調的值,並傳入下一個next。
這樣能成功拿到咱們要的值,可是太麻煩了。因而就有了generator+co的組合!
安裝:
$ npm install co
複製代碼
使用:
co(r()).then(data=> {
console.log(data)
})
複製代碼
co會迭代執行it.next()方法,直到done的布爾值爲true就返回generator函數的運行結果。
大體執行代碼以下:
function co(it) {
return new Promise((resolve, reject)=> {
function next(data) {
let {value, done} = it.next(data)
if(done){
resolve(value)
}else{
value.then(data=> {
next(data)
},reject)
}
}
next()
})
}
複製代碼
async 函數是Generator 函數的語法糖。
比Generator函數用起來簡單
async function r() {
try{
let r1 = await read('1.txt','utf8')
let r2 = await read(r1,'utf8')
let r3 = await read(r2,'utf8')
return r3
}catch(e){
console.log('e',e)
}
}
r().then(data=> {
console.log(data)
},err=>{
console.log('err',err)
})
複製代碼
async 函數返回一個 Promise 對象,可使用 then 方法添加回調函數。遇到await就會先返回,等待函數執行。