1、Promise调用链
Promise是一个有限状态自动机
Promise在收到既定事件后,会根据预设的规则进行转换状态,转换完后执行相应回调
Promise 拥有3种状态
PENDING 初始状态
FULFILLED 已兑现
REJECTED 已拒绝
转换规则
fulfill / resolve 将 PENDING 转换为 FULFILLED (已敲定)
reject 将 PENDING 转换为 REJECTED (已敲定)
处于已敲定 settled 状态下的 Promise 的状态将不能再变化了
状态转换后所执行的回调
Promise 被拒绝 执行 onRejected 回调函数
即 then 的第二个函数参数 或 catch 的第一个函数参数
Promise 被兑现 执行 onFulFilled 回调函数
即 then 的第一个函数参数
若 then 中无函数参数,则会跳过Promise链的这一环到下一环的Promise中去
事件举例 ajax & Promise
我们用xhr对象去请求一张图片,为了避免繁琐的回调写法(callback hell),我们使用 Promise。
代码如下,这里 Promise 接收的事件便是 xhr 的 load 事件和 error 事件,收到事件后,Promise 的状态会按照业务逻辑与预设规则进行转换,并执行转换后的回调,从而触发整个 Promise 调用链。
function get_img(url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.onload = function() {
if(xhr.status===200 || xhr.status===304) resolve();
else reject(new Error("ERROR CODE: " + xhr.statusText));
};
xhr.onerror = function(){
reject(new Error("network error"));
}
xhr.send();
});
}
顺便也总结下 xhr 的使用方式:创、开、配、达、错、发。
2、Promise使用技巧
Promise 的朴素用法是 创建 Promise 对象,配置 Promise 状态转换规则,使得 事件A发生时兑现承诺,事件B发生时拒绝承诺等等,用 then / catch 执行状态转换后的回调,并返回一个新 Promise,再循环执行上述操作,直至处理完所有的事件。
举一个例子,查询一位同学的分数,需要用已知的学号 id 查到同学在的班级 class,再用 class 与 id 按某算法查到同学的座位号 seat,再用座位号 seat 查到我们要的结果分数 score,
伪代码如下
let id = "ID";
let _class = 0, _seat = 0, _score = 0;
let id = "ID";
let _class = 0, _seat = 0, _score = 0;
new Promise(function (resolve, reject) {
_class = "CLASS";
console.log(_class)
let success = true;
if(success) resolve(_class);
else reject(err);
})
.then(function (_class){
new Promise(function(resolve, reject) {
_seat = "SEAT";
console.log(_seat);
let success = false;
if(success) resolve(_seat);
else reject(_seat);
})
})
.then(function (_seat){
new Promise(function(resolve, reject) {
_score = "SCORE";
console.log(_score);
let success = true;
if(success) resolve(_score);
else reject(err);
})
})
.catch(function(err) {
throw new Error("network error:" + err);
})
.finally(function() {});
3、异步的其他方法
异步的写法除了有Promise之外,还有 事件回调、观察者模式 两种写法。
我们拿 node.js 处理输入输出的场景举个例子吧,node 的输入输出是异步的,因此常规的写法是事件回调,如下:
const readline = require("readline");
const R = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const LOG = console.log;
function _sort(nums) {
return nums.sort((a,b) => a-b);
}
// CallBack
R.on("line", function(line) {
line = line.trim();
let nums = line.split(" ");
for(let i=0;i<nums.length;i++) nums[i]-='0';
let res = _sort(nums);
LOG(res);
});
callback的写法的问题是需要深层嵌套,形成回调地域,Promise等新语法就是为了解决这个问题的。
接下来是 Promise 的写法:
// 公共代码
const readline = require("readline");
const R = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const LOG = console.log;
function _sort(nums) {
return nums.sort((a,b) => a-b);
}
// Promise
new Promise(function(resolve, reject) {
R.on("line", function(line) {
resolve(line.trim());
});
})
.then(function(line) {
let nums = line.split(" ");
for(let i=0;i<nums.length;i++) nums[i] -= '0';
let res = _sort(nums);
LOG(res);
})
.catch(function(rejection) {
LOG("some error happened: " + rejection);
})
Promise的调用链能够保证按我们想要的顺序进行执行程序,我们也可以使用async,await对上述代码改写:
// Ansyc + Await
async function fn() {
try {
let line = await new Promise(function(resolve, rejection) {
R.on("line", function(line) {
resolve(line.trim());
});
});
let nums = line.split(" ");
for(let i=0;i<nums.length;i++) nums[i]-='0';
let res = _sort(nums);
LOG(res);
}
catch(rejection) {
LOG("some error has happened: " + rejection);
}
}
fn();
最终,我们用观察者模式实现一遍:
// Observable
const readline = require("readline");
const R = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const LOG = console.log;
function _sort(nums) {
return nums.sort((a,b) => a-b);
}
function Observable() {
this.cbs = new Map(); // Map: k -> [fn1, fn2, ...]
}
Observable.prototype.add = function(e_name, cb) {
if(!this.cbs.has(e_name)) this.cbs.set(e_name, []);
let tmp = this.cbs.get(e_name);
tmp.push(cb);
}
Observable.prototype.emit = function(e_name, ...res) {
if(this.cbs.has(e_name)) {
for(let fn of this.cbs.get(e_name)) {
if(e_name === "line") {
console.log(res);
let [line] = res;
fn(line);
}
else {
try{
fn();
}catch(e) {
LOG("some error has happened: " + e);
}
}
}
}
}
Observable.prototype.CONSTANTS = {
LINE : "line",
HTTP : "http"
};
let observable = new Observable();
R.on("line", function(line) {
observable.emit(observable.CONSTANTS.LINE, line.trim());
});
(function dosort(line) {
observable.add("line", function cb(line) {
let nums = line.split(" ");
for(let i=0;i<nums.length;i++) nums[i]-='0';
let res = _sort(nums);
LOG(res);
})
})();