首先一提到Promise 我們首先想到的就是非同步程式設計。非同步程式設計在JavaScript中我們最早接觸的是callback形式的非同步,callback形式的非同步程式設計最大的特點就是地獄式回撥巢狀,一旦巢狀次數過多,就很容易使我們的程式碼難以理解和維護。而Permise的出現是為了更好的解決JavaScript中非同步程式設計的問題。
js中非同步的方式有哪些?
1.回撥函式callback形式:
$.get(URL,function(res) {
if(res) {
$.get(URL,function(res) {
if(res) {
$.get(URL,function(res) {
if(res) {
}
});
}
});
}
});
複製程式碼
這就是傳說中的回撥地獄!!!
複製程式碼
2.ES6 Promise 物件:
let p = new Promise(function (resolve, reject) {
resolve('第一次成功了')
});
p.then(function (res) { //第一次promise的結果
console.log(res)
let next_p = new Promise(function (resolve, reject) {
reject('第二次失敗功了')
});
return next_p
}, function (err) {
console.log(err)
}).then(function (res) { //第二次permise的結果
console.log(res)
}, function (err) {
console.log(err)
})
複製程式碼
上面是簡單的一個 es6 promise物件的用法 程式碼結構上來看,並沒有像 callback回撥那樣出現回撥地獄的形式,而是常見的鏈式呼叫,(如jquery)。
複製程式碼
3.ES6 Generator 函式 :
Generator 函式有多種理解角度。語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態.
執行 Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,
可以依次遍歷 Generator 函式內部的每一個狀態。
形式上,Generator 函式是一個普通函式,但是有兩個特徵:
一是,function關鍵字與函式名之間有一個星號;
二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)
複製程式碼
function* read() {
console.log(1);
let a = yield '第一次';
console.log(a);
let b = yield '第二次'
console.log(b);
return b;
}
let it = read();
console.log(it.next('111')); // {value:'第一次',done:false}
console.log(it.next('222')); // {value:'第二次',done:false}
console.log(it.next('333')); // {value:'333',done:true}
複製程式碼
Generator 應用場景 主要還是要與promise 配合一起使用
Generator 和 co:
nodejs出現了 co 模組,它基於 ES6 的 generator 和 yield ,讓我們能用同步的形式編寫非同步程式碼。
複製程式碼
co(function *() {
var data = yield $.get('/api/data');
console.log(data);
var user = yield $.get('/api/user');
console.log(user);
var products = yield $.get('/api/products');
console.log(products);
});
複製程式碼
以上的 Promise 和 generator 最初創造它的本意都不是為了解決非同步流程控制。其中 Promise 是一種程式設計思想,用於“當xx資料準備完畢,
then執行xx動作”這樣的場景,不只是非同步,同步程式碼也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 創造性的拿來做非同步流程
控制了
複製程式碼
4.ES7 async / await 語法糖
async / await co + generator的語法糖
async / await 解決的問題有哪些?
1.回撥地獄
2.併發執行非同步,在同一時刻同步返回結果 Promise.all()
3.解決了返回值的問題
4.可以實現程式碼的try/catch;
複製程式碼
async function r(){
try{
let content1 = await read('./2.promise/100.txt','utf8');
let content2 = await read(content1,'utf8');
return content2;
}catch(e){ // 如果出錯會catch
console.log('err',e)
}
}
// async函式返回的是promise,
r().then(function(res){
console.log(res);
},function(err){
console.log(err);
})
複製程式碼
Promise 扮演著一個承上啟下的作用,非常關鍵,我們動手寫一個自己的Promise!
---->(fulfilled)成功態 --->resolve();
|
new Promise ----(pending) 初始化為等待態
|
---->(rejected)失敗態 --->reject();
複製程式碼
function Promise(executor) {
let _this = this;
//promise的 三種狀態
_this.status = 'pending'; //等待態 初始值 例項化promise一開始時是等待狀態 唯一性
_this.value = undefined; //成功態 表示成功時要傳的值 初始預設值
_this.error = undefined; //失敗態 表示失敗時要傳的值
_this.onFufilledCallbacks = []; // 存放then成功的回撥函式
_this.onRejectedCallbacks = []; // 存放then失敗的回撥函式
//成功的方法
function resolve(value) {
if (_this.status === 'pending') {
_this.status = 'fulfilled'; //當呼叫 resolve()時改為成功態
_this.value = value;
//遍歷 存放then成功的回撥函式 並一次呼叫
_this.onFufilledCallbacks.forEach(function (itemFn) {
itemFn();
});
};
};
//失敗的方法
function reject(error) {
if (_this.status === 'pending') {
_this.status = 'rejected'; //當呼叫 reject()時改為失敗態
_this.error = error;
_this.onRejectedCallbacks.forEach(function (itemFn) {
itemFn();
});
}
};
//處理 例項化時 throw new err('錯誤') 需要走reject();!!!
try {
executor(resolve, reject); //executor執行器,包含兩個引數,分別是resolve() 和reject(),new Promise這個executor就會立即執行
} catch (err) { //捕獲時發生異常,就直接失敗
reject(err)
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 有可能這裡返回的x是別人的promise
// 儘可能允許其他亂寫
if (promise2 === x) { //這裡應該報一個型別錯誤,有問題
return reject(new Typeerr('迴圈引用了'))
}
// 看x是不是一個promise,promise應該是一個物件
let called; // 表示是否呼叫過成功或者失敗
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是promise {},看這個物件中是否有then方法,如果有then我就認為他是promise了
try { // {then:1}
let then = x.then;
if (typeof then === 'function') {
// 成功
then.call(x, function (y) {
if (called) return
called = true
// y可能還是一個promise,在去解析直到返回的是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, function (err) { //失敗
if (called) return
called = true
reject(err);
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(e);
}
} else { // 說明是一個普通值1
resolve(x); // 表示成功了
}
}
// then方法註冊 當resolve(成功)/reject(失敗)的回撥函式
Promise.prototype.then = function (onFufilled, onRejected) {
//成功和失敗預設不傳給一個函式
onFufilled = typeof onFufilled === 'function' ? onFufilled : function (value) {
return value;
}
onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
throw err;
}
let _this = this;
let promise2; //返回的promise 處理 .then() 的鏈式呼叫 返回一個新的promise物件
if (_this.status === 'fulfilled') {
promise2 = new Promise(function (resolve, reject) {
// 當成功或者失敗執行時有異常那麼返回的promise應該處於失敗狀態
// x 是return的返回值 可能是一個promise 也可能是一個普通值
setTimeout(function () { //用setTimeout來模擬非同步,真正的es6promise不是用setTimeout來實現的
try {
let x = onFufilled(_this.value)
// x可能是別人promise,寫一個方法統一處理
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err);
}
});
});
};
if (_this.status === 'rejected') {
//當成功或者失敗執行時有異常時 那麼返回的 promise2應該處於失敗狀態
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(_this.error); // x 是return的返回值 可能是一個promise 也可能是一個普通值
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err);
}
});
});
};
//當呼叫 then 時 有可能沒成功也沒失敗 還是pending 需要把回撥先存起來
if (_this.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
_this.onFufilledCallbacks.push(function () {
setTimeout(function () {//用setTimeout來模擬非同步,真正的es6promise不是用setTimeout來實現的
try {
let x = onFufilled(_this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
});
_this.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRejected(_this.error);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
});
});
};
return promise2
}
複製程式碼
使用自己的封裝的promise:
複製程式碼
let p = new Promise(function(resolve,reject) {
resolve('success');
});
p.then(function(data) {
console.log('成功:',data)
return 'zhanghw'
},function(err) {
console.log('失敗:',err)
}).then(function(data) {
console.log(data)
},function(err) {
console.log(err)
})
複製程式碼
列印結果:
成功: success
zhanghw
複製程式碼