Promise原理講解 async+await應用(非同步回撥解決方案)
1.非同步程式設計
在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函式實現方法
function after(times, callback){ return function(){ //次數一直減少 if(--times == 0){ callback(); } } } let fn = after(3, function(){ console.log('after 被呼叫了三次'); }) fn(); fn(); fn();
接下來就是常見的讀取資料的問題,回撥函式的話,我們只能一層一層往下讀取,很容易就進入了回撥地獄
這個可怕的狀態
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節點上面繫結過事件函式,那就使用過釋出—訂閱模式。
document.body.addEventListener('click',function(){ alert(2); },false); document.body.click();//模擬使用者點選
實現原理
首先用一個數組arr儲存回撥函式,然後觸發emit的時候,arr裡面的回撥函式一一執行
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 觀察者模式
觀察者模式 釋出和訂閱的 被觀察者是依賴於觀察者的
//觀察者 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
變為rejected
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.1Promise.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.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.2Promise.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.3Promise.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.4Promise.race
Promise.race
方法同樣是將多個Promise
例項,包裝成一個新的Promise
例項。
const p = Promise.race([p1, p2, p3]);
上面程式碼中,只要p1
、p2
、p3
之中有一個例項率先改變狀態,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.5Promise.resolve
Promise.resolve方法允許呼叫時不帶引數,直接返回一個resolved狀態的 Promise 物件。
const p = Promise.resolve(); p.then(function () { // ... });
實現原理
Promise.resolve = function(value){ return new Promise((resolve, reject)=>{ resolve(value); }) }
2.6Promise.reject
Promise.reject方法允許呼叫時不帶引數,直接返回一個rejected狀態的 Promise 物件。
const p = Promise.reject(); p.then(function () { // ... });
實現原理
Promise.reject = function(reason){ return new Promise((resolve, reject)=>{ reject(reason); }) }
2.7promise
的一些擴充套件庫
ofollow,noindex" target="_blank">bluebird
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+
測試程式碼是否符合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;
參考文件
ECMAScript 6 入門 Promise--阮一峰