js 的引擎建立在单线程事件轮询( single threaded event loop)概念之上。
单线程意味着在一段时间内只能执行一段代码。所以我们不需要追踪这些运行的代码,而是在他们准备好执行时将他们放置到任务队列(job queue)。当代码由 js 引擎执行完毕后,引擎通过 event loop 找到并执行队列任务中的下一个任务。
当一个用户点击了一个按钮或按下一个键盘上的某个按键时,一个事件如 onclick 会被触发。为了响应该交互,或许一个新的任务会被添加到任务队列中。这就是 JavaScript 异步编程中最基本的形式。关于处理事件的代码知道事件发生后才会执行,此时相应的上下文(context)会出现,例如:
let button = document.getElementById("my-btn");
button.onclick = function(event) {
console.log("Clicked");
};
但是 如果按钮在注册之前被点击,那么什么事情都不会发生。由此,我们必须保证所有相应的处理程序在事件第一次放生之前注册完毕。
例如 nodejs:
readFile("example.txt", function(err, contents) {
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!");
});
});
调用 readFile() 成功之后又出现了另一个异步调用 —— writeFile()
该模式使用起来感觉相当不错,不过当回调函数嵌套过多时,你很快就会发现自己陷入了回调地狱(callback hell)。像这样:
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(result);
});
});
});
});
缺点:
promise 是异步操作结果的占位符,函数可以返回一个 promise,而不用订阅一个事件或向函数传递回调参数,like this:
// readFile 承诺在之后完成该操作
let promise = readFile("example.txt");
在这段代码中,readFile() 会稍后而非立即去读取文件。函数会返回一个 promise 对象来表示异步读取操作以便在之后你可以使用它。确切使用 promise 结果的时机完全取决于 promise 生命周期中的行为。
一开始都会处于短暂的挂起(pending)状态,表示操作仍未完成,即挂起的 promise 被认为是未定的(unsettled),一旦操作完成,promise 就被认为是已定的(settled),并处于一下两种状态之一:
以该种方式实现 then() 方法的对象被称为 thenable 。所有 promise 都是 thenable,但不是所有的的 thenable 都是 promise 。
let promise = readFile("example.txt");
promise.then(
function(contents) {
// fulfillment
console.log(contents);
},
function(err) {
// rejection
console.error(err.message);
}
);
promise.then(function(contents) {
// fulfillment
console.log(contents);
});
promise.then(null, function(err) {
// rejection
console.error(err.message);
});
// 它的行为等效于只传递 rejection 处理(handler)
promise.catch(function(err) {
// rejection
console.error(err.message);
});
// 等效于:
promise.then(null, function(err) {
// rejection
console.error(err.message);
});
then()和 catch()都是来处理异步操作的。这种机制比事件和回调更优的原因是:
promise 操作结果成功或失败是一目了然的。
事件出现错误后不会被触发。
回调函数总是要查看 error 参数。
/**
* resolve() 函数会在执行函数成功运行后发出信号表示该 promise 已经可用
* reject() 函数代表改执行函数运行失败。
* Promise(function(resolve,reject){
* // 异步任务 do something
* // resolve()
* // or 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");
/**
* 同时监听 fulfillment 和 rejection
* promise.then(处理成功,处理失败)
*/
promise.then(
function(contents) {
// fulfillment
console.log(contents);
},
function(err) {
// rejection
console.error(err.message);
}
);
需要注意的是,执行函数在 readFile()被调用后会立即执行。
当 resolve() 和 reject() 在执行函数内部被调用后,为了处理这个 promise,一个任务会被放置在任务队列中。这种行为叫做任务调度(job scheduling),在任务调度中,你向任务队列添加一个任务并声明:现在不要执行它。
可以看一个例子体会一下:
let promise = new Promise(function(resolve, reject) {
console.log("Promise");
resolve();
});
promise.then(function() {
console.log("Resolved.");
});
console.log("Hi!");
该例子的输出为:
Promise
Hi!
Resolved
Promise.resolve()方法接收单个参数并返回一个 fufilled 状态的 promise。这意味着没有发生任务调度。
let promise = Promise.resolve(42);
promise.then(function(value) {
console.log(value); // 42
});
Promise.reject() 方法来创建状态为 rejected 的 promise 。
let promise = Promise.reject(42);
promise.catch(function(value) {
console.log(value); // 42
});
了解即可。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
let thenable = {
then: function(resolve, reject) {
reject(42);
}
};
let p1 = Promise.resolve(thenable);
p1.catch(function(value) {
console.log(value); // 42
});
实际上,每一次调用 then() 和 catch() 都会返回另一个 promise。
它只会在之前的 promise 转化为 fulfilled 或 rejected 状态的那一刻后才会被处理 。
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1
.then(function(value) {
console.log(value);
})
.then(function() {
console.log("Finished");
});
代码输出:
42;
Finished;
promise 链允许你捕获上一个 promise 的 fulfillment 或 rejection 处理中的错误。例如:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1
.then(function(value) {
throw new Error("Boom!");
})
.catch(function(error) {
console.log(error.message); // "Boom!"
});
机制:promise 链中的 promise 能够向下一个 promise 传递数据
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1
.then(function(value) {
console.log(value); // "42"
return value + 1;
})
.then(function(value) {
console.log(value); // "43"
});
在 promise 链中返回一个 promise:
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
p1
.then(function(value) {
// 首个 fulfillment 处理
console.log(value); // 42
return p2;
})
.then(function(value) {
// 第二个 fulfillment 处理
console.log(value); // 43
});
认识该模式重要的一点:第二个 fulfillment 处理并没有添加在 p2,而是第三个 promise 上。
ECMAScript 6 提供了两个方法负责此事:Promise.all() 和 Promise.race()
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.then(function(value) {
console.log(Array.isArray(value)); // true
console.log(value[0]); // 42
console.log(value[1]); // 43
console.log(value[2]); // 44
});
记住:all()方法是 当所有的 promise (p1,p2,p3) fulfilled 之后 (p4)才会转变状态为 fulfilled。如果某个 promise 转变为 rejected 状态,那么 p4 会立即返回一个 rejected 状态的 promise 而不用等待其它 promise 完成执行。
let p1 = Promise.resolve(42);
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
resolve(44);
});
let p4 = Promise.race([p1, p2, p3]);
p4.then(function(value) {
console.log(value); // 42
});
与 all 不同的是:传给 Promise.race() 的 promise 真如竞赛一般看哪一个先处于已定状态,并返回胜出的 fulfilled promise;p4 的 fulfillment 处理会被立即调用并传入值 42,其它的 promise 都被忽略。
仅做了解:
假如,你想创建一个包含 success() 和 failure() 方法的 promise 但又不想丢掉内置版本中的 then() 和 catch(),你可以如下创建该 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(42);
});
promise
.success(function(value) {
console.log(value); // 42
})
.failure(function(value) {
console.log(value);
});
let fs = require("fs");
function run(taskDef) {
// 创建迭代器
let task = taskDef();
// 开始任务
let result = task.next();
// 使用函数递归进行迭代
(function step() {
// 如果还有工作要做
if (!result.done) {
// 使用 resolve() 来简化 promise 的处理
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("config.json");
doSomethingWith(contents);
console.log("Done");
});
它的基本理念是使用 async 标记的函数和 await 而不是生成器与 yield 来调用函数,例如:
(async function() {
let contents = await readFile("config.json");
doSomethingWith(contents);
console.log("Done");
});
学习链接 : https://oshotokill.gitbooks.io/understandinges6-simplified-chinese/content/chapter_11.html