非同步程式設計的幾種形式:
回撥函式形式:
function f1(callback){
callback();
}
function f2(callback){
callback();
}
function f3(callback){
callback();
}
f1(f2(f3))
複製程式碼
這種方式實現非同步程式設計優點是思路清晰,以序列的思考方式進行程式設計,缺點是形成回撥地獄,過多的回撥巢狀使得程式碼變得難以理解拆分和維護。
釋出訂閱模式
let dep = {
list: [],
on: function (fn) {
list.push(fn);
},
emit: function () {
this.list.forEach(event => {
typeof event === 'function' ? event() : null;
})
}
};
複製程式碼
上面就是簡易版的釋出訂閱模式:釋出者存在一個陣列list用於登記訂閱者即非同步執行的函式,等到一定條件下執行emit,訂閱的非同步函式都會執行。這就好比釋出者售樓中心的擁有一個登記冊,裡面登記需要買房的所有訂閱者,有的訂閱者登記的是電話通知,有的訂閱者登記的郵件通知,等到樓盤資訊變化時會主動給每個訂閱者執行相應的操作(執行對應的函式)
promise
promise的核心原理其實就是釋出訂閱模式,通過兩個佇列來快取成功的回撥(onResolve)和失敗的回撥(onReject)。如果還不熟悉promise用法的朋友,請參考Es6入門之promise物件,下面來分析promise的特點。
promise的特點:
- new Promise時需要傳遞一個executor執行器,執行器會立刻執行
- 執行器中傳遞了兩個引數:resolve成功的函式、reject失敗的函式,他們呼叫時可以接受任何值的引數value
- promise狀態只能從pending態轉onfulfilled,onrejected到resolved或者rejected,然後執行相應快取佇列中的任務
- promise例項,每個例項都有一個then方法,這個方法傳遞兩個引數,一個是成功回撥onfulfilled,另一個是失敗回撥onrejected
- promise例項呼叫then時,如果狀態resolved,會讓onfulfilled執行並且把成功的內容當作引數傳遞到函式中
- promise中可以同一個例項then多次,如果狀態是pengding 需要將函式存放起來 等待狀態確定後 在依次將對應的函式執行 (釋出訂閱)
promise基礎版實現
下面針對這些特點來實現promise:
new Promise時需要傳遞一個executor執行器,執行器會立刻執行;執行器中傳遞了兩個引數:resolve成功的函式、reject失敗的函式,他們呼叫時可以接受任何值的引數value
function Promise (executor){
function resolve(value){}
function reject(value){}
try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
var promise = new Promise((resolve,reject)=>{
console.log('start');
})
複製程式碼
promise狀態只能從pending態轉onfulfilled,onrejected到resolved或者rejected
function Promise (executor) {
var self = this;//resolve和reject中的this指向不是promise例項,需要用self快取
self.state = 'padding';
self.value = '';//快取成功回撥onfulfilled的引數
self.reson = '';//快取失敗回撥onrejected的引數
self.onResolved = []; // 專門存放成功的回撥onfulfilled的集合
self.onRejected = []; // 專門存放失敗的回撥onrejected的集合
function resolve (value) {
if(self.state==='padding'){
self.state==='resolved';
self.value=value;
self.onResolved.forEach(fn=>fn())
}
}
function reject (reason) {
self.state = 'rejected';
self.value = reason;
self.onRejected.forEach(fn=>fn())
}
try{
executor(resolve,reject)
}catch(e){
reject(e)
}
}
複製程式碼
promise例項,每個例項都有一個then方法,這個方法傳遞兩個引數,一個是成功回撥onfulfilled,另一個是失敗回撥onrejected;
promise例項呼叫then時,如果狀態resolved,會讓onfulfilled執行並且把成功的內容當作引數傳遞到函式中;
promise中可以同一個例項then多次,如果狀態是pengding 需要將函式存放起來 等待狀態確定後 在依次將對應的函式執行(釋出訂閱)
Promise.prototype.then=function (onfulfilled,onrejected) {
var self=this;
if(this.state==='resolved'){
onfulfilled(self.value)
}
if(this.state==='rejected'){
onrejected(self.value)
}
if(this.state==='padding'){
this.onResolved.push(function () {
onfulfilled(self.value)
})
}
}
複製程式碼
then方法的特點:
以上只是實現了promise的基本功能,但是還缺少then的鏈式呼叫,then函式引數onfulfilled,onrejected預設處理,鏈式呼叫返回值多種情況的處理,下面分析then方法的特點:
- 因為promise狀態確定後就是不能更改,所以每次promise執行then後都會返回一個新的promise而不是this,那麼狀態永遠為resolve或jeject,將存在非同步呼叫
- onfulfilled或onrejected是一個可選引數,需要做沒有傳遞時的處理
- 如果then中onfulfilled或onrejected返回的是一個普通值的話會把這個結果傳遞下一次then中的成功回撥
- 如果then中onfulfilled或onrejected出現異常,會走下一個then的失敗回撥,將err傳遞到失敗回撥中
- 如果失敗後還可以成功,如果返回undefined,會把undefined傳遞給下一次
- 如果then方法返回的是一個promise,那麼會等待這個promise執行完決定返回的是成功還是失敗
promise優化版實現
因為promise狀態確定後就是不能更改,所以每次promise執行then後都會返回一個新的promise而不是this,那麼狀態永遠為resolve或jeject,將存在非同步呼叫;onfulfilled或onrejected是一個可選引數,需要做沒有傳遞時的處理
Promise.prototype.then=function (onfulfilled,onrejected) {
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;//onfulfilled預設處理
onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};//onrejected預設處理
var self=this,promise2=new Promise(function(resolve,reject){//返回一個promise
if(this.state==='resolved'){
try{
onfulfilled(self.value);//裡面會執行resolve
}catch(e){
reject(e);
}
}
if(this.state==='rejected'){
try{
onrejected(self.value);
}catch(e){
reject(e);
}
}
if(this.state==='padding'){//將執行過程快取
self.onResolved.push(function () {
try{
onfulfilled(self.value);
}catch(e){
reject(e)
}
});
self.onRejected.push(function () {
try{
onrejected(self.value);
}catch(e){
reject(e)
}
})
}
})
return promise2;
}
複製程式碼
如果then中onfulfilled或onrejected返回的是一個普通值的話會把這個結果傳遞下一次then中的成功回撥;
如果then中onfulfilled或onrejected出現異常,會走下一個then的失敗回撥,將err傳遞到失敗回撥中;
如果失敗後還可以成功,如果返回undefined,會把undefined傳遞給下一次;
如果then方法返回的是一個promise,那麼會等待這個promise執行完決定返回的是成功還是失敗,所以需要對onfulfilled或onrejected的返回值進行處理。
Promise.prototype.then=function (onfulfilled,onrejected) {
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;
onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};
var self=this,
res=null,//用來快取onfulfilled或onrejected的返回值
promise2=new Promise(function(resolve,reject){
if(this.state==='resolved'){
try{
res = onfulfilled(self.value);//得到onfulfilled的返回值
resolvePromise(promise2,res,resolve,reject);//返回值的處理函式
}catch(e){
reject(e);
}
}
if(this.state==='rejected'){
try{
res = onrejected(self.value);//得到onrejected的返回值
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e);
}
}
if(this.state==='padding'){
self.onResolved.push(function () {
try{
res = onfulfilled(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e)
}
});
self.onRejected.push(function () {
try{
res = onrejected(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e)
}
})
}
})
return promise2;
}
function resolvePromise(promise,res,resolve,reject) {
if(promise===res){//防止迴圈引用
return reject(new TypeError('迴圈引用'))
}
let called;//防止重複執行
if(res!==null&&(typeof res==='function'||typeof res ==='object')){
try {//防止promise執行報錯
let then=res.then;//判斷是否promise就判斷是否存在then方法
if(typeof then ==='function'){//如果返回的是promise,只需要在返回的promise的then方法中下一步需要執行的函式
then.call(res,(res2)=>{
if (called) return;
called = true;
resolvePromise(promise,res2,resolve,reject);//如果是promise繼續遞迴執行,直到不是promise,依次執行外層的resolve,讓promise狀態改變
},)
}else{//如果不是promise,有可能是undefine、onfulfilled或onrejected的返回的普通值,就直接將這個值返回,將外層的promise狀態改變
if (called) return;
called = true;
resolve(then)
}
}catch(e){
if (called) return;
called = true;
reject(e)
}
}else{
resolve(res)
}
};
複製程式碼
promise.then屬於非同步微任務,then中的方法,必須等到所有的同步任務執行完才執行,巨集任務和微任務可以檢視EventLoop其實如此簡單
console.log(1);
var promise=new Promise(function(resolve,reject){
resolve('a');
})
promise.then(function(value){
console.log(value)
})
console.log(2);
// 1 2 'a'
複製程式碼
//由於promise有內部的機制實現微任務,所以這裡使用setTimeout代替
Promise.prototype.then=function (onfulfilled,onrejected) {
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;
onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};
var self=this,
res=null,
promise2=new Promise(function(resolve,reject){
if(this.state==='resolved'){
setTimeout(()=>{
try{
res = onfulfilled(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e);
}
})
}
if(this.state==='rejected'){
setTimeout(()=>{
try{
res = onrejected(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e);
}
})
}
if(this.state==='padding'){
self.onResolved.push(function () {
setTimeout(()=>{
try{
res = onfulfilled(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e);
}
})
});
self.onRejected.push(function () {
setTimeout(()=>{
try{
res = onrejected(self.value);
resolvePromise(promise2,res,resolve,reject);
}catch(e){
reject(e);
}
})
})
}
})
return promise2;
}
複製程式碼
promise.catch會捕獲到沒有捕獲的異常;
Promise.resolve會返回一個狀態位rsolved的promise;
Promise.reject會返回一個狀態位rsolved的promise;
Promise.all會在所有的promise resolved後執行回撥,Promise.race只要有一個promise resolved就執行回撥。
promise.catch將所有的錯誤在promise例項的下一個then中返回
Promise.prototype.catch = function (onrejected) {
return this.then(null, onrejected)
};
複製程式碼
Promise.reject和Promise.reject
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
};
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
resolve(value);
})
};
複製程式碼
Promise.all和Promise.race
//在每個promise的回撥中新增一個判斷函式processData(就是在當前的promise.then中新增),每個promise狀態改變後讓index++,直到和promises的個數相等就執行回撥
Promise.all=function (promises) {
return new Promise((resolve,reject)=>{
let results=[],i=0;
for(let i=0;i<promises.length;i++){
let p=promises[i];
p.then((data)=>{
processData(i,data)
},reject)
}
function processData (index,data) {
results[index]=data;
if(++i==promises.length){
resolve(results)
}
}
})
};
//在每個promise的回撥中新增一個resolve(就是在當前的promise.then中新增),有一個狀態改變,就讓race的狀態改變
Promise.race=function (promises) {
return new promises((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
let p=promises[i];
p.then(resolve,reject)
}
})
};
複製程式碼
generator + co
Generator函式可以通過yield暫停執行和next恢復執行,所以可以封裝一個函式來自動執行next函式而使Generator完成非同步任務。
let fs = require('mz/fs');
function * read() {
let age = yield fs.readFile('./name.txt','utf8');
let adress = yield fs.readFile(age,'utf8');
let r = yield fs.readFile(adress,'utf8');
return r;
}
function co(it) {//it為一個generator物件
//返回一個promise並且執行generator物件的next
return new Promise((resolve,reject)=>{
function next(data) {
//獲取前一個非同步函式的返回值,其必須為promise
let { value, done } = it.next(data);
if(!done){
//返回promise的then方法中新增generator的next
//前一個非同步函式執行成功會使返回的promise成resolved
//然後執行generator的next,遞迴依次類推
value.then(data=>{
next(data)
}, reject);
}else{
resolve(value);
}
}
next();
})
}
co(read()).then(data=>{
console.log(data);
},err=>{
console.log(err);
});
複製程式碼
async和awit
async+awit等於generator+co,而co中實現generator自動化是基於Promise,所以awit會使用new Promise形式。
結語
如果以上有任何錯誤之處,希望提出並請指正,如果對promise使用還不清楚的朋友,請參考Es6入門之promise物件,本文參考:
- Promise A+規範
- Es6入門之promise物件
- Promise 原始碼
- 教你一步步實現promise