【读书笔记】【深入理解ES6】#11-Promise与异步编程
异步编程的背景知识
JavaScript 引擎是基于单线程(Single-threaded)实际循环的概念构建的,同一时刻只允许一个代码块在执行。
所以需要跟踪即将运行的代码,那些代码被放在一个任务队列(job queue)中,每当一段代码准备执行时,都会被添加到任务队列。每当 JavaScript 引擎中的一段代码结束执行,事件循环(event loop)会执行队列中的下一个任务,它是 JavaScript 引擎中的一段程序,负责监控代码执行并管理任务队列。队列中的任务会从第一个一直执行到最后一个。
事件模型
用户点击按钮或者按下按钮会触发类似 onclick 这样的事件,它会向任务队列添加一个新任务来响应用户的操作,这是 JavaScript 中最基础的异步编程形式,直到事件触发时才执行事件处理程序,且执行时上下文与定义时的相同。
let button = document.getElementById("my-btn");
button.onclick = function(event) {
console.log("Clicked");
}
单击 button 后赋值给 onclick 的函数被添加到任务队列中,只有当前面的任务都完成后它才会被执行。
回调模式
回调模式与事件模式类似,异步代码都会在未来的某个时间点执行,两者的区别是回调函数中被调用的函数是作为参数传入的。
readFile("example.txt", function(err, contents) {
// 错误(error first)优先
if (err) {
throw err;
}
console.log(contents); // 文件读取结束后执行
});
Console.log("Hi!"); // 先执行
回调模式比事件模型更灵活,因为相比之下,通过回调模式链接多个调用更容易。
readFile("example.txt", function(err, contents) {
if (err) {
throw err;
}
// 再次使用回调函数
writeFile("example.txt", function(err) {
if (err) {
throw err;
}
console.log("File was written!");
})
console.log(contents);
});
虽然上例中的模式运行的不错,但是当嵌套层数过多时将陷入回调地狱。
method1(function(err, result) {
if (err) {
throw err;
}
method2(function(err, result) {
if (err) {
throw err;
}
method3(function(err, result) {
if (err) {
throw err;
}
method4(function(err, result) {
if (err) {
throw err;
}
method5(reuslt);
})
})
})
})
如果想实现更复杂的操作,回调函数的局限性同样会显现出来。
例如,同步执行两个异步操作,当两个操作都结束时通知你;或者同时进行两个异步操作,只取优先完成的操作结果。
Promise 的基础知识
Promise 相当于异步操作结果的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个 Promise。
// readFile承诺将在未来的某个时刻完成
let promise = readFile("example.txt");
readFile不会立即执行,函数会先返回一个表示异步读取操作的 Promise 对象,未来对这个对象的操作完全取决于 Promise 周期。
Promise 生命周期
- unsettled(未处理)
- pending(进行中)
- settled(已处理)
- fulfilled(成功完成)
- rejected(未成功完成)
内部属性 [[PromiseState]] 被用来表示 Promise 的三种状态:pending、fulfilled、rejected。这个属性不暴露在 Promise 对象上,所以不能以编程的方式检测 Promise 的状态,只有当 Promise 的状态改变时,通过 then() 方法来采取特定的行动。
所有的 Promise 都有 then() 方法,它接受两个参数:一个是当 Promise 的状态变为 fulfilled 时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数(fulfillment function);第二个是当 Promise 的状态变为 rejected 时要调用的函数,其与完成时调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数(rejection function)。
Note
如果一个对象实现了上述的 then() 方法,那这个对象我们称之为 thenable 对象。所有的 Promise 都是 thenable 对象,但并非所有 thenable 对象都是 Promise。
let promise = readFile("example.txt");
promise.then(function(contents) {
// 完成
console.log(contents);
}, function(err) {
// 拒绝
console.error(err.message);
});
promise.then(function(contents) {
// 完成
console.log(contents);
});
promise.then(null, function(err) {
// 拒绝
console.error(err.message);
});
Promise 还有一个 catch() 方法,相当于只给其传入错误处理程序的 then() 方法。
promise.then(function(err) {
// 拒绝
console.error(err.message);
});
// 与以下调用相同
promise.then(null, function(err) {
// 拒绝
console.error(err.message);
})
then()方法和catch()方法一起使用才能更好地处理异步操作结果。
如果一个 Promise 处于已处理状态,在这之后添加到任务队列中的处理程序仍将执行。
let promise = readFile("example.txt");
// 最初的完成处理程序
promise.then(function(contents) {
console.log(contents);
// 现在又添加一个到任务队列中
promise.then(function(contents) {
cosnole.log(contents);
})
})
Note
每次调用 then() 方法或 catch() 方法都会创建一个新任务,当 Promise 被解决(resolved)时执行。这些任务最重会被加入到一个为 Promise 量身定做的独立队列中,这个任务队列的具体细节对应理解如何使用 Promise 而言不重要,通常你只要理解任务队列是如何运作的就可以了。
创建未完成的 Promise
用 Promise 构造函数可以创建新的 Promise,构造函数只接受一个参数:包含初始化 Promise 代码的执行器(executor)函数。
执行器接受两个参数,分别是 resolve() 函数和 reject() 函数。
执行器成功时调用 resolve() 函数,失败时调用 reject() 函数。
// Node.js 示例
let fs = require("fs");
function readFile(filename) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
if (err) {
reject(err);
return;
}
resolve(contents);
});
});
}
let promise = readFile("example.txt");
promise.then(function(contents) {
// 完成
console.log(contents);
}, function(err) {
// 拒绝
console.error(err.message);
});
使用 Promise 构造函数创建 Promise 时,Promise 的执行器会立即执行,然后才执行后续流程中的代码。
执行器中调用 resolve() 后会触发一个异步操作,传入 then() 和 catch() 方法的函数会被添加到任务队列中并异步执行。
let promise = new Promise(function(resolve, reject) {
console.log("Promise");
resolve();
});
promise.then(function() {
console.log("Resolved.");
});
console.log("Hi!");
// 输出内容
// Promise
// Hi!
// Resolved.
虽然 promise.then() 在 console.log("Hi!") 之前,但仍然在其之后执行。这是因为完成处理程序和拒绝处理程序总是在执行器完成后被添加到任务队列的末尾。
创建已处理的 Promise
使用 Promise.resolve()
Promise.resolve() 方法只接受一个参数并返回一个完成态的 Promise。
let promise = Promise.resolve(32);
promise.then(function(value) {
console.log(value); // 32
});
使用 Promsie.reject()
可以通过 Promsie.reject() 方法创建已拒绝 Promise。它与 Promise.resolve() 很像,唯一的区别是创建出来的是拒绝态的 Promise。
let promise = Promise.reject(32);
promise.catch(function(value) {
console.log(value); // 32
});
非 Promise 的 Thenable 对象
Promise.resolve() 和 Promise.reject() 方法都可以接受非 Promise 的 Thenable 对象作为参数。如果传入一个非 Promise 的 Thenable 对象,则这些方法会创建一个新的 Promise,并在 then() 函数中被调用。
拥有 then() 方法并且接受 resolve 和 reject 这两个参数的普通对象就是非 Promise 的 Thenable 对象。
let thenable = {
then: function(resolve, reject) {
resolve(32);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 32
})
执行器错误
如果执行器内部抛出一个错误,则 Promise 的拒绝处理程序就会被调用。
let promise = new Promise(function(resolve, reject) {
throw new Error("Explosion!");
});
promise.catch(function(error) {
console.log(error.message); // Explosion!
});
每个执行器都隐含了一个 try-catch 块,所以错误会被捕获并传入拒绝处理程序。
let promise = new Promise(function(resolve, reject) {
try {
throw new Error("Explosion!");
} catch (ex) {
reject(ex);
}
});
promise.catch(function(error) {
console.log(error.message); // Explosion!"
})
全局的 Promise 拒绝处理
如果在没有拒绝处理程序的情况下拒绝一个Promise,那么不会提示失败信息,这是 JavaScript 语言中唯一一处没有强制报错的地方。
Promise 的特性决定了很难检测一个 Promise 是否被处理过。
let rejected = Promise.reject(32);
// 此时, rejected 还没有被处理
rejected.catch(function(value) {
// 现在 rejected 已经被处理了
console.log(value);
});
任何时候都可以调用 then() 方法或 catch() 方法,无论 Promise 是否已解决,这两个方法都可以正常运行,但这样很难知道一个 Promise 何时被处理。
Node.js 环境的拒绝处理
在 Node.js 中,处理 Promise 拒绝时会触发 process 对象上的两个事件:
- unhandledRejection
在一个事件循环中,当 Promise 被拒绝,并且没有提供拒绝处理程序时被调用。 - rejectionHandled
在一个事件循环后,当 Promise 被拒绝,并且没有提供拒绝处理程序时被调用。
设计这些事件是用来识别那些拒绝却又没被处理的过的 Promise 的。
unhandledRejection 事件处理程序接受两个参数:拒绝原因及被拒绝的 Promise。
let rejected;
process.on("unhandledRejection", function(reason, promise) {
console.log(reason.message); // "Explosion!"
console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
rejectionHandled 事件处理程序接受一个参数:被拒绝的 Promise。
let rejected;
process.on("rejectionHandled", function(promise) {
console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
// 等待添加拒绝处理程序
setTimeout(function() {
rejected.catch(function(value) {
console.log(value.message); // "Explosion!"
});
}, 1000);
// 输出结果
// true
// Explosion!
浏览器环境的拒绝处理程序
浏览器也是通过触发两个事件来识别未处理的拒绝的,虽然这些事件是在 window 对象上触发的,但实际上与 Node.js 中的完全等效。
- unhandledrejection
在一个事件循环中,当 Promise 被拒绝,并且没有提供拒绝处理程序时被调用。 - rejectionhandled
在一个事件循环后,当 Promise 被拒绝,并且没有提供拒绝处理程序时被调用。
事件处理程序接受一个有以下属性的对象作为参数:
- type
事件名称("unhandledrejection" 或 "rejectionhandled") - promise
被拒绝的 Promise 对象 - reason
来自 Promise 的拒绝值
let rejected;
window.onunhandledrejection = function(event) {
console.log(event.type); // "unhandledrejection"
console.log(event.reason.message); //
console.log(rejected === event.promise); // true
};
window.onrejectionhandled = function(event) {
console.log(event.type); // "rejectionhandled"
console.log(event.reason.message); //
console.log(rejected === event.promise); // true
};
rejected = Promise.reject(new Error("Explosion!"));
Note
在Chrome上运行没有执行自定义的异常处理。按照网上的文档,Chrome 49+ 应该是支持该特性的,本机装的版本是61,但是仍然没有效果。
串联 Promise
每次调用 then() 方法或 catch() 方法时,实际上创建并返回了另一个 Promise,只有当第一个 Promise 完成或被拒绝后,第二个才会被解决。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
p1.then(function(value) {
console.log(value);
}).then(function() {
console.log("Finished");
});
// 输出结果
// 32
// Finished
只有第一个 then() 方法被执行后,才会调用第二个 then() 方法。上例可以拆解成如下形式:
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = p1.then(function(value) {
console.log(value);
});
p2.then(function() {
console.log("Finished");
});
// 输出结果
// 32
// Finished
捕获错误
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
p1.then(function(value) {
throw new Error("Boooom!");
}).catch(function(error) {
console.log(error.message); // "Boooom!"
}).then(function() {
console.log("Finished"); // Finished
});
// 输出结果
// Boooom!
// Finished
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
p1.then(function(value) {
throw new Error("Boooom!");
}).catch(function(error) {
console.log(error.message); // "Boooom!"
}).then(function() {
console.log("Finished"); // "Finished"
});
也可以通过后面的拒绝处理程序捕获前面的拒绝处理程序中的错误。
let p1 = new Promise(function(resolve, reject) {
throw new Error("Explosion!");
});
p1.catch(function(error) {
console.log(error.message); // "Explosion!"
throw new Error("Boooom!");
}).catch(function(error) {
console.log(error.message); // "Boooom!"
}).then(function() {
console.log("Finished"); // "Finished"
});
// 输出结果
// Explosion!
// Boooom!
// Finished
Note
务必在 Promise 链的末尾留有一个拒绝处理程序以确保能够正确处理所有可能发生的错误。
Promise 链的返回值
Promise 链的另一个重要特性是可以给下游 Promise 传递数据。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
p1.then(function(value) {
console.log(value); // 32
return value + 1;
}).then(function(value) {
console.log(value); // 33
});
在拒绝处理程序中也可以做相同的事情。
let p1 = new Promise(function(resolve, reject) {
reject(32);
});
p1.catch(function(value) {
console.log(value); // 32
return value + 1;
}).then(function(value) {
console.log(value); // 33
});
在 Promise 链中返回 Promise
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = new Promise(function(resolve, reject) {
resolve(33);
});
p1.then(function(value) {
// 第一个完成处理程序
console.log(value); // 32
return p2;
}).then(function(value) {
// 第二个完成处理程序
console.log(value); // 33
});
关于这个模式,最需要注意的是,第二个完成处理程序被添加到了第三个Promise而不是p2。上面的示例等价于:
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = new Promise(function(resolve, reject) {
resolve(33);
});
let p3 = p1.then(function(value) {
// 第一个完成处理程序
console.log(value); // 32
return p2;
});
p3.then(function(value) {
// 第二个完成处理程序
console.log(value); // 33
});
将 p2 改成被拒绝,p3.then() 将永远不被执行。。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = new Promise(function(resolve, reject) {
reject(33);
});
let p3 = p1.then(function(value) {
// 第一个完成处理程序
console.log(value); // 32
return p2;
});
p3.then(function(value) {
// 第二个完成处理程序
console.log(value); // 永不执行
});
虽然使用的 return p2 返回给变量 p3,但 p2 是不等于 p3,是两个不同的变量。但是 p2 和 p3 的 [[PromiseStatus]] 及 [[PromiseValue]] 属性的值时一致的。也就是说,如果 p2 被拒绝了,那么 p3 也是被拒绝的。最终导致了上例中的 p3.then() 永远不会被调用。
在完成处理程序中创建新的 Promise 可以推迟完成处理程序的执行。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
p1.then(function(value) {
console.log(value); // 32
// 创建一个新的 promise
let p2 = new Promise(function(resolve, reject) {
resolve(33);
});
return p2;
}).then(function(value) {
console.log(value); // 33
});
跟前一个例子的区别在于,p2 只会在 p1 被解决后才会被执行。
相应多个 Promise
如果想通过监听多个 Promise 来决定下一步的操作,则可以使用 ECMAScript 6 提供的 Promise.all() 和 Promise.race() 两个方法来监听多个 Promise。
Promise.all() 方法
Promise.all() 方法只接受一个参数并返回一个 Promise,该参数是一个含有多个受监视 Promise 的可迭代对象,只有当可迭代对象中所有 Promise 都被解决后返回的 Promise 才会被解决,只有当可迭代对象中所有 Promise 都被完成后返回的Promise才会被完成。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = new Promise(function(resolve, reject) {
resolve(33);
});
let p3 = new Promise(function(resolve, reject) {
resolve(34);
});
let p4 = Promise.all([p1, p2, p3]);
p4.then(function(value) {
console.log(Array.isArray(value)); // true
console.log(value[0]); // 32
console.log(value[1]); // 33
console.log(value[2]); // 34
});
所有传入 Promise.all() 方法的 Promise 只要有一个被拒绝,那么返回的 Promise 没等所有 Promise 都完成就立即被拒绝。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = new Promise(function(resolve, reject) {
reject(33);
});
let p3 = new Promise(function(resolve, reject) {
resolve(34);
});
let p4 = Promise.all([p1, p2, p3]);
p4.catch(function(value) {
console.log(Array.isArray(value)); // false
console.log(value); // 33
});
拒绝处理程序总是接受一个值而非数组,该值来自被拒绝 Promise 的拒绝值。
Promise.race() 方法
它也接受含多个受监视 Promise 的可迭代对象作为唯一参数并返回一个 Promise,但只要有一个 Promise 被解决返回的 Promise 就被解决,无需等到所有 Promise 都被完成。
一旦数组中的某个 Promise 被完成,Promise.race() 方法也会像 Promise.all() 方法一样返回一个特定的 Promise。
let p1 = Promise.resolve(32);
let p2 = new Promise(function(resolve, reject) {
resolve(33);
});
let p3 = new Promise(function(resolve, reject) {
resolve(34);
});
let p4 = Promise.race([p1, p2, p3]);
p4.then(function(value) {
console.log(value); // 32
});
传给 Promise.race() 方法的 Promise 会进行竞选,以决出哪一个先被解决,如果先解决的是已完成 Promise,则返回已完成 Promise;如果先解决的是已拒绝 Promise,则返回已拒绝 Promise。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = Promise.reject(33);
let p3 = new Promise(function(resolve, reject) {
resolve(34);
});
let p4 = Promise.race([p2, p1, p3]);
p4.catch(function(value) {
console.log(value); // 33
});
自 Promise 继承
Promise 与其它内建类型一样,也可以作为基类派生其他类。
class MyPromise extends Promise {
success(resolve, reject) {
return this.then(resolve, reject);
}
failure(reject) {
return this.catch(reject);
}
}
let promise = new MyPromise(function(resolve, reject) {
resolve(32);
});
promise.success(function(value) {
console.log(value); //32
}).failure(function(value) {
console.log(vlaue);
});
MyPromise 类型派生自 Promise,并且扩展了两个方法 success() 和 failure()。
由于静态方法会被继承,因此派生的 Promise 也拥有 MyPromise.resolve()、MyPromise.reject()、MyPromise.race() 和 MyPromise.all() 这4个方法,后两者与内建方法完全一致,而前两者却稍有不同。
由于 MyPromise.resolve() 方法和 MyPromise.reject() 方法通过 Symbol.species 属性来决定返回 Promise 的类型,故调用这两个方法时无论传入什么值都会返回一个 MyPromise 的实例。
let p1 = new Promise(function(resolve, reject) {
resolve(32);
});
let p2 = MyPromise.resolve(p1);
p2.success(function(value) {
console.log(value); // 32
});
console.log(p2 instanceof MyPromise); // true
// 输出结果:
// true
// 32
基于 Promise 的异步任务执行
let fs = require("fs");
function run(taskDef) {
// 创建可以在其他地方使用的迭代器
let task = taskDef();
// 开始执行任务
let result = task.next();
// 不断调用next()的递归函数
function step() {
// 如果有更多任务要做
if (!result.done) {
if(typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
result = task.throw(err);
return;
}
result = task.next(data);
step();
});
} else {
result = task.next(result.value);
step();
}
}
}
// 启动递归
step();
}
// 定义一个可用于执行的函数
function readFile(filename) {
return function(callback) {
fs.readFile(filename, callback);
};
}
// 执行一个任务
run(function*() {
let contents = yield readFile("example.txt");
// doSomethingWith(contents);
console.log(contents);
console.log("Done");
});
将上例中的异步操作改成使用 Promise 实现。
let fs = require("fs");
function run(taskDef) {
// 创建迭代器
let task = taskDef();
// 开始执行任务
let result = task.next();
// 不断调用next()的递归函数
(function step() {
// 如果有更多任务要做
if (!result.done) {
let promise = Promise.resolve(result.value);
promise.then(function(value) {
result = task.next(value);
step();
}).catch(function(error) {
result = task.throw(error);
step();
});
}
}());
}
// 定义一个可用于执行的函数
function readFile(filename) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, contents) {
if (err) {
reject(err);
} else {
resolve(contents);
}
});
});
}
// 执行一个任务
run(function*() {
let contents = yield readFile("example.txt");
// doSomethingWith(contents);
console.log(contents);
console.log("Done");
});
【读书笔记】【深入理解ES6】#11-Promise与异步编程的更多相关文章
- Promise和异步编程
前面的话 JS有很多强大的功能,其中一个是它可以轻松地搞定异步编程.作为一门为Web而生的语言,它从一开始就需要能够响应异步的用户交互,如点击和按键操作等.Node.js用回调函数代替了事件,使异步编 ...
- Promise对象 异步编程
Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大.所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是 ...
- 《深入理解ES6》笔记—— Promise与异步编程(11)
为什么要异步编程 我们在写前端代码时,经常会对dom做事件处理操作,比如点击.激活焦点.失去焦点等:再比如我们用ajax请求数据,使用回调函数获取返回值.这些都属于异步编程. 也许你已经大概知道Jav ...
- 20150206读书笔记<深入理解计算机系统>
●第一章 C是系统级编程的首选.C++显示支持抽象,属于应用级程序设计语言. 简单例子: 一个典型系统的硬件组成: 存储器的层次结构: 注:存储器层次结构的设计思想是,该层存储器作为下一层存储器的高速 ...
- 深入理解es6的promise
一.promise入门 1. Promise对象是什么 回调函数的另一种原生实现,比之前回调函数的写法机构清晰,功能强大, 2.以前回调这么写 function a(fn){ let h = 1; s ...
- 《android开发艺术探索》读书笔记(十四)--JNI和NDK编程
接上篇<android开发艺术探索>读书笔记(十三)--综合技术 No1: Java JNI--Java Native Interface(java本地接口),它是为了方便java调用C. ...
- es6 generator函数的异步编程
es6 generator函数,我们都知道asycn和await是generator函数的语法糖,那么genertaor怎么样才能实现asycn和await的功能呢? 1.thunk函数 将函数 ...
- python 进阶读书笔记1 -- 理解python一切皆对象
理解python一切皆对象: 1.所有的类都是由type创建的 2.所有的类的基类都是object 3.type是类,也是实例,type的基类是object,type对象是由type创建的 4.obj ...
- 【读书笔记::深入理解linux内核】内存寻址【转】
转自:http://www.cnblogs.com/likeyiyy/p/3837272.html 我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0 ...
随机推荐
- Python 安装 BeautifulSoup(Win7)
准备材料: 1.Win7,已安装的 Python3.4.1 2.BeautifulSoup4.3.2安装包 安装办法: 1.打开cmd 2,进入BeautifulSoup的解压文件夹 3,执行 pyt ...
- 003Java语言环境搭建
JRE,JDK JRE(Java Runtime Environment java运行环境):包括java虚拟机和java程序所需要的核心类库, 如果要运行一个开发好的java程序,计算机中只需要安装 ...
- GIT如何从本地上传代码到github
转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/46738929 本文出自:[海龙的博客] 开篇之前说下题外话,之前写过一 ...
- css盒子模型(3)
盒子模型 版权声明 本文原创作者:雨点的名字 作者博客地址:https://home.cnblogs.com/u/qdhxhz/ 在讲理论之前,我们先要知道网页设计中常听的属性名:内容(co ...
- DotNetCore跨平台~Moq框架实现模拟测试
回到目录 当我们进行软件开发时,一般会写单元测试,而对于业务情景来说,一般是测试它的业务逻辑准确性,对于你的测试数据是否来自数据库还是文件,是否为真实还是模拟,并不是很关心!我关心的就是我的业务逻辑是 ...
- 【adb】连接BlueStacks
1.在任务管理器中找到
- php-删除非空目录
function deldir($path){ if(!is_dir($path)){ return false; } $dh = opendir($path); while(($file = rea ...
- django xdmin使用
我们来看看我们原先django给我们自带的admin后台是什么样子的呢 有人说,你的界面怎么那么丑,我说这个还叫丑吗,他说丑,我说你来,我看看你的,上图 看到登录界面后,我说别看了,我去修改,修改,我 ...
- Python第二十四天 binascii模块
Python第二十四天 binascii模块 binascii用来进行进制和字符串之间的转换 import binascii s = 'abcde' h = binascii.b2a_hex(s) # ...
- asp.net MVC里的 ModelState使用方法
https://www.cnblogs.com/hohoa/p/5839993.html if (!ModelState.IsValid) { string error = string.Empty; ...