Promise原理 async+await應用(異步回調解決方案)

1.異步編程

JavaScript的世界中,全部代碼都是單線執行的。 因爲這個「缺陷」,致使JavaScript的全部網絡操做,瀏覽器事件,都必須是異步執行。異步執行能夠用:javascript

  • 回調函數
  • 發佈訂閱
  • 觀察者模式
  • promise

1.1.回調函數
function call(id, callback){
  return function(){
     callback(id+1);
  }
}
let fn = call(3, function(id){
  console.log(id);
})
fn();
複製代碼

lodash 裏面的after函數實現方法html

function after(times, callback){
  return function(){
    //次數一直減小
    if(--times == 0){
      callback();
    }
  }
}
let fn = after(3, function(){
  console.log('after 被調用了三次');
})
fn();
fn();
fn();
複製代碼

接下來就是常見的讀取數據的問題,回調函數的話,咱們只能一層一層往下讀取,很容易就進入了回調地獄這個可怕的狀態java

let fs = require('fs');
let school = {}
fs.readFile('./age.txt', 'utf8', function (err, data) {
    school['name'] = data;
    fs.readFile('./name.txt', 'utf8', function (err, data) {
      school['age'] = data;//{ name: 'cjw', age: '18' }
    });
});

複製代碼
1.2 發佈訂閱

發佈者和訂閱者是沒有依賴關係的
你可能對發佈訂閱有點陌生,其實只要在DOM節點上面綁定過事件函數,那就使用過發佈—訂閱模式。node

document.body.addEventListener('click',function(){
  alert(2);
},false);
document.body.click();    //模擬用戶點擊
複製代碼

實現原理
首先用一個數組arr保存回調函數,而後觸發emit的時候,arr裏面的回調函數一一執行es6

let fs = require('fs');

let dep = {
    arr: [],//保存回調函數
    on(callback){
        this.arr.push(callback);
    },
    emit(){
        this.arr.forEach(item=>{
            item();
        })
    }
}

let school = {};
//這裏先加一個回調函數 (訂閱)
dep.on(function(){
    if(Object.keys(school).length === 2){
        console.log(school);//{ name: 'cjw', age: '18' }
    }
})
//
fs.readFile('./age.txt', 'utf8', function(err, data){
    school['name'] = data;
    dep.emit();//發佈,調用dep.arr 裏面的回調函數一一執行
})
fs.readFile('./name.txt', 'utf8', function(err, data){
    school['age'] = data;
    dep.emit();//發佈
})

複製代碼
1.3 觀察者模式

觀察者模式 發佈和訂閱的 被觀察者是依賴於觀察者的web

//觀察者
class Observer{
    constructor(){
        this.arr = [];
        this.val = 1;
    }
    updateVal(val){
        this.val = val;
        this.notify();
    }
    notify(){
        this.arr.forEach(s=>s.update());
    }
    save(s){//保存一個對象
        this.arr.push(s);
    }
}
// 被觀察者,被觀察者有一個更新的方法。
class Subject{
    update(){
        console.log('update')
    }
}
let s = new Subject();
let observer = new Observer();
observer.save(s);//保存一個對象
observer.save(s);
observer.updateVal(21);//更新值的時候,被觀察者也執行一個更新的方法
複製代碼
1.4 Promise

promise有如下兩個特色:
1.對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。
2.一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejectednpm

let fs = require('fs');
function read(url){
    return new Promise((resolve, reject)=>{
        fs.readFile(url, 'utf8', (err, data)=>{
            if(err) reject(err);
            resolve(data);
        })
    })
}
let school = {};
read('./name.txt').then(data=>{
    school['name'] = data;
    return read('age.txt');
}).then(data=>{
    school['age'] = data;
    console.log(school);//{ name: 'cjw', age: '18' }
})
複製代碼

2.promise用法與原理

2.1 Promise.prototype.then()

Promise 實例具備then方法,也就是說,then方法是定義在原型對象編程

//let Promise = require('./promise.js');
let p = new Promise((resolve, reject)=>{
    setTimeout(function(){     
        reject('成功');
    },100)
    reject('3');
})
p.then((value)=>{
    console.log(value);//3,這裏是3由於,只能從一個狀態panding到另外一個狀態
}, (reason)=>{
    console.log(reason);
})
複製代碼

基本概念
數組

1.new Promise時須要傳遞一個executor執行器,執行器會馬上執行
2.執行器中傳遞了兩個參數 resolve成功的函數 他調用時能夠傳一個值 值能夠是任何值 reject失敗的函數 他調用時能夠傳一個值 值能夠是任何值
3.只能從pending態轉到成功或者失敗
4.promise實例。每一個實例都有一個then方法,這個方法傳遞兩個參數,一個是成功另外一個是失敗
5.若是調用then時 發現已經成功了會讓成功函數執行而且把成功的內容看成參數傳遞到函數中
6.promise 中能夠同一個實例then屢次,若是狀態是pengding須要將函數存放起來 等待狀態肯定後 在依次將對應的函數執行 (發佈訂閱)
7.若是類執行時出現了異常 那就變成失敗態
promise

Promise.prototype.then()的實現

function Promise(executor){
    var self = this;
    self.status = 'pending';//從pending 轉換爲resolved rejected
    self.value = undefined;
    self.reason = undefined;
    self.onResolved = [];//專門存放成功的回調
    self.onRejected = [];//專門存放失敗的回調
    //pending -> resolved
    function resolve(value){
        if(self.status === 'pending'){
            self.value = value;
            self.status = 'resolved';
            self.onResolved.forEach(fn=>fn());
        }
    }
     //pending -> rejected
    function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
            self.onRejected.forEach(fn=>fn());
        }
    }
    try{
        executor(resolve, reject);
    }catch(e){
        reject(e);
    }
}
//then方法的實現
Promise.prototype.then = function(onfulfilled, onrejected){
   let self = this;
   if(self.status === 'resolved'){//判斷狀態,resolved時候,返回value
       onfulfilled(self.value);
   }
   if(self.status === 'rejected'){//判斷狀態,rejected時候,返回reason
       onrejected(self.reason);
   }

   if(self.status === 'pending'){
      self.onResolved.push(function(){
          onfulfilled(self.value);
      })
      self.onRejected.push(function(){
        onfulfilled(self.reason);
      })
   }
}
module.exports = Promise;
複製代碼
2.2 Promise.prototype.catch()

Promise.prototype.catch方法.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

let p = new Promise((resolve, reject)=>{
    resolve();
})
p.then(data=>{
    throw new Error();
}).then(null).catch(err=>{
    console.log('catch', err)
}).then(null, err=>{
    console.log('err', err);
})
複製代碼

實現原理

Promise.prototype.catch = function(onrejected){
    return this.then(null, onrejected);
}
複製代碼
2.3 Promise.all

Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3]);
複製代碼

實現原理

Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let results = []; let i = 0;
        function processData(index, data) {
        results[index] = data; // let arr = []  arr[2] = 100
        if (++i === promises.length) {
            resolve(results);
        }
        }
        for (let i = 0; i < promises.length; i++) {
        let p = promises[i];
        p.then((data) => { // 成功後把結果和當前索引 關聯起來
            processData(i, data);
        }, reject);
        }
    })
}
複製代碼
2.4 Promise.race

Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.race([p1, p2, p3]);
複製代碼

上面代碼中,只要p1p2p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

let Promise = require('./e2.promise');
let fs = require('mz/fs');

Promise.race([
    fs.readFile('./age.txt', 'utf8'),
    fs.readFile('./name.txt', 'utf8')
]).then(data=>{
    console.log(data);
})
複製代碼

實現原理

Promise.race = function(promises){
    return new Promise((resolve, reject)=>{
        for(let i=0; i< promises.length; i++){
            let p = promises[i];
            p.then(resolve, reject);
        }
    })
}
複製代碼
2.5 Promise.resolve

Promise.resolve方法容許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。

const p = Promise.resolve();

p.then(function () {
  // ...
});
複製代碼

實現原理

Promise.resolve = function(value){
    return new Promise((resolve, reject)=>{
        resolve(value);
    })
}
複製代碼
2.6 Promise.reject

Promise.reject方法容許調用時不帶參數,直接返回一個rejected狀態的 Promise 對象。

const p = Promise.reject();

p.then(function () {
  // ...
});
複製代碼

實現原理

Promise.reject = function(reason){
    return new Promise((resolve, reject)=>{
        reject(reason);
    })
}
複製代碼
2.7 promise的一些擴展庫

bluebird

mz

2.8 應用 async + await = generator + co

generator 生產迭代器的
生成器函數 * generator 通常配合 yield

function * read() {
    yield 1;
    yield 2;
    yield 3;
    return 100
}
let it = read();
console.dir(it.next());
console.dir(it.next());
console.dir(it.next());
console.dir(it.next());
//結果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 100, done: true }
複製代碼

promise + generator

let fs = require('mz/fs');
// let co = require('co');

function * read(){
    let age = yield fs.readFile('./age.txt', 'utf8');
    return age;
}

//co 原理
function co(it){
    return new Promise((resolve, reject)=>{
        function next(data){
            let { value, done } = it.next(data);
            if(!done){
                value.then(data=>{
                    next(data);
                }, reject)
            }else{
                resolve(value);
            }
        }
        next();
    })
}

co(read()).then(data=>{
    console.log(data);//18
}, err=>{
    console.log(err);
})
複製代碼

async + await是es7的語法

let fs = require('mz/fs');//這個mz庫將nodejs裏面的fs所有函數都promise化
// async 函數就是promise es7
// 回調的問題 不能try/catch 併發問題
async function read() { 
    let age = await fs.readFile('name.txt','utf8')
    return age
}
read().then(data=>{
  console.log(data);//cjw
})
複製代碼

3.手寫一個promise A+

promise A+ 規範傳送門

測試代碼是否符合a+ 規範 爲了讓其能測試

npm install promises-aplus-tests -g
promises-aplus-tests 文件名 能夠測試
複製代碼
/*
 * @Author: caijw 
 * @Date: 2018-10-01 15:04:43 
 * @Last Modified by: caijw
 * @Last Modified time: 2018-10-08 22:41:06
 */
function Promise(executor){
    var self = this;
    self.status = 'pending';//從pending 轉換爲resolved rejected
    self.value = undefined;
    self.reason = undefined;
    self.onResolved = [];//專門存放成功的回調
    self.onRejected = [];//專門存放失敗的回調
    //pending -> resolved
    function resolve(value){
        if(self.status === 'pending'){
            self.value = value;
            self.status = 'resolved';
            self.onResolved.forEach(fn=>fn());
        }
    }
     //pending -> rejected
    function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
            self.onRejected.forEach(fn=>fn());
        }
    }
    try{
        executor(resolve, reject);
    }catch(e){
        reject(e);
    }
}

//這裏主要就是遞歸循環,判斷是否爲promise,若是是promise就繼續遞歸循環下去。
function resolvePromise(promise2, x, resolve, reject){
    if(promise2 === x){
        return reject(new TypeError('循環引用'));
    }

    let called;
    if(x!=null && (typeof x === 'object' || typeof x === 'function')){
        try{
            let then = x.then;
            //假設他是一個promise,then方法就是一個函數
            if(typeof then === 'function'){
                then.call(x, (y)=>{
                    if(called) return;
                    called = true;
                    // 遞歸解析 若是resolve的是一個promise 就要不停的讓resolve的結果進行處理
                    resolvePromise(promise2, y, resolve, reject);
                },(e)=>{
                    if(called) return;
                    called = true;
                    reject(e);
                })
            }else{//不是就返回
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
        
    }else{
        resolve(x);
    }
}





//至返回錯誤的 catch 就是不寫成功的回調的then方法
Promise.prototype.catch = function(onrejected){
    return this.then(null, onrejected);
}

//1.解決輸出的順序的問題
// all方法的參數 是一個數組,會按照數組的結果放到成功的回調裏(只有全成功纔算成功)
// race方法參數也是一個數組。會同時發起併發,可是以返回最快的結果爲結果


Promise.race = function(promises){
    return new Promise((resolve, reject)=>{
        for(let i=0; i< promises.length; i++){
            let p = promises[i];
            p.then(resolve, 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 = function (promises) {
    return new Promise((resolve, reject) => {
        let results = []; let i = 0;
        function processData(index, data) {
        results[index] = data; // let arr = []  arr[2] = 100
        if (++i === promises.length) {
            resolve(results);
        }
        }
        for (let i = 0; i < promises.length; i++) {
        let p = promises[i];
        p.then((data) => { // 成功後把結果和當前索引 關聯起來
            processData(i, data);
        }, reject);
        }
    })
}



//回調函數
Promise.prototype.then = function(onfulfilled, onrejected){
    // onfulfilled / onrejected是一個可選的參數
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled :  val=>val;
    onrejected = typeof onrejected === 'function' ? onrejected :err => {
        throw err;
    }
   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)
        }
        if(self.status === 'rejected'){
            setTimeout(()=>{
                try{
                    let x = onrejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                }catch(e){
                    reject(e);
                }
            }, 0)
            
        }
        if(self.status === 'pending'){
            self.onResolved.push(function(){
                setTimeout(()=>{
                    try{
                        let x = onfulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                }, 0)
            })
            self.onRejected.push(function(){
                setTimeout(()=>{
                    try{
                        let x = onrejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                }, 0)
            })
        }
   })
   return promise2;
}
// 語法糖 簡化問題 嵌套的問題 ,被廢棄了
Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve, reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

module.exports = Promise;
複製代碼

終於擼完promise了,小夥伴們看了,感受有收穫,請點個

參考文檔

ECMAScript 6 入門 Promise--阮一峯

Hey, 你的Promise

ES6版Promise實現,給你不同的體驗

使用 Bluebird 開發異步的 JavaScript 程序

相關文章
相關標籤/搜索