標籤:兩種 cte 參考 方法參數 value 觀察 返回 點擊 executor
- 1.非同步編程
- 1.1.回呼函數
- 1.2 發布訂閱
- 1.3 觀察者模式
- 1.4 Promise
- 2.promise用法與原理
- 2.1
Promise.prototype.then()
- 2.2
Promise.prototype.catch()
- 2.3
Promise.all
- 2.4
Promise.race
- 2.5
Promise.resolve
- 2.6
Promise.reject
- 2.7
promise的一些擴充庫
- 2.8 應用
async + await = generator + co
- 3.手寫一個promise A+
- 參考文檔
1.非同步編程
在JavaScript的世界中,所有代碼都是單線執行的。 由於這個“缺陷”,導致JavaScript的所有網路操作,瀏覽器事件,都必須是非同步執行。非同步執行可以用:
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.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.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]);
上面代碼中,只要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.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 -gpromises-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--阮一峰
Hey, 你的Promise
ES6版Promise實現,給你不一樣的體驗
使用 Bluebird 開發非同步 JavaScript 程式
Promise原理講解 async+await應用(非同步回調解決方案)