跳到主要内容

原理-异步流程

Ajax

最早异步的概念是随着 ajax 的发展而产生的, 即那些稍后执行的代码块. 最为简单的就是使用 js 中的setTimeOut函数来定义一个某个个固定时间以后的代码. 当这段代码推迟的时间不确定的时候, setTimeOut 就变得不适用了. 所以出现了 ajax.

假设我们有一个ajax()函数, 其中可以再稍后执行一些代码. 像这样:

// ajax(..) 是某个包中任意的Ajax函数
ajax('http://some.url.1', function myCallbackFunction(data) {
console.log(data); // Yay, 我得到了一些`data`!
});

异步在流程上比同步要思考更多的东西, 比如并发, 互动, 协作等.

并发的 ajax:

var res = {};

function foo(results) {
res.foo = results;
}

function bar(results) {
res.bar = results;
}

// ajax(..) 是某个包中任意的Ajax函数
ajax('http://some.url.1', foo);
ajax('http://some.url.2', bar);

互动的并发:

var a, b;

function foo(x) {
a = x * 2;
if (a && b) {
baz();
}
}

function bar(y) {
b = y * 2;
if (a && b) {
baz();
}
}

function baz() {
console.log(a + b);
}

// ajax(..) 是某个包中任意的Ajax函数
ajax('http://some.url.1', foo);
ajax('http://some.url.2', bar);

其中baz()调用周围的if条件通常称为"大门", 但在打开大门之前需要等待他们全部到达. 另一种可能并发的互动状态有时称为"竞争", 但更准确的叫做"门闩", 特点是先到者胜.

var a;

function foo(x) {
if (a == undefined) {
a = x * 2;
baz();
}
}

function bar(x) {
if (a == undefined) {
a = x / 2;
baz();
}
}

function baz() {
console.log(a);
}

// ajax(..) 是某个包中任意的Ajax函数
ajax('http://some.url.1', foo);
ajax('http://some.url.2', bar);

Ajax 是最经典的回调式的异步解决方案, 但也存在一些问题, 比如回调地狱等. 一般来说, 回调有两种设计:

  1. 提供分离的回调(一个用作成功的通知, 一个用作错误的通知), 例如 jquery 的 ajax, ES6 的 promise 等
  2. 错误优先风格: 一个回调以一个参数作为错误对象保留, 如果成功, 这个对象是空, 否则将被设置为 truthy, 并且通常不再传递其他的东西.

Promise

Promise 是 ES6 的新语法, 提供了一种新的异步处理方式:

const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise 的本意是承诺, 一般来说:

  • 一旦 Promsie 被解析, 它就变成了一个不可变的值, 并且可以根据需要被监听任意多次
  • Promise 一旦被定义就会立即执行且无法停止.
  • 任何拥有then()方法的对象或函数都可以用鸭子类型的方法来认为它是一个 promise.
  • 提供给then()的会调用总是会被异步的调用
  • 一旦一个 Promise 被解析, 所有在then()上注册的回调都将被立即的, 按顺序的, 在下一个异步机会会被调用, 而且并没有任何在这些回调中发生的事可以影响/推迟其他回调的回调.
p.then(function() {
p.then(function() {
console.log('C');
});
console.log('A');
});
p.then(function() {
console.log('B');
});
// A B C

这里的 C 是无法干扰并且优先于 B 的.

使用Promise.race可以给Promise设定超时检测:

// 一个检测Promise超时的工具
function timeoutPromise(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject('Timeout!');
}, delay);
});
}

// 为`foo()`设置一个超时
Promise.race([
foo(), // 尝试调用`foo()`
timeoutPromise(3000) // 给它3秒钟
]).then(
function() {
// `foo(..)`及时地完成了!
},
function(err) {
// `foo()`不是被拒绝了,就是它没有及时完成
// 那么可以考察`err`来知道是哪种情况
}
);

并且then本身返回另一个Promise, 可以用来组成链式调用:

function delay(time) {
return new Promise( function(resolve,reject){
setTimeout( resolve, time );
} );
}

delay( 100 ) // step 1
.then( function STEP2(){
console.log( "step 2 (after 100ms)" );
return delay( 200 );
} )
.then( function STEP3(){
console.log( "step 3 (after another 200ms)" );
} )
.then( function STEP4(){
console.log( "step 4 (next Job)" );
return delay( 50 );
} )
.then( function STEP5(){
console.log( "step 5 (after another 50ms)" );
} )
...

此外 Promise 被定义为只能解析一次, 如果创建代码尝试调用多次的resolve或者reject多次, 那么 Promise 只接受第一次解析, 而忽略后续的尝试.

Promise 的解析只能传递一个参数, 多余的参数会被忽略, 没有参数就是 undefined.

在 Promise 创建过程中如果抛出了一个错误, 这个错误将被捕获, 并且当前的 Promise 强制变为拒绝, 还有一个细节:

var p = new Promise(function(resolve, reject) {
resolve(42);
});

p.then(
function fulfilled(msg) {
foo.bar();
console.log(msg); // 永远不会跑到这里 :(
},
function rejected(err) {
// 也永远不会跑到这里 :(
}
);

Promise 排程

链接在两个分离的 Promise 上的回调之间的相对顺序是不能可靠预测的. 比如这样:

var p3 = new Promise(function(resolve, reject) {
resolve('B');
});

var p1 = new Promise(function(resolve, reject) {
resolve(p3);
});

var p2 = new Promise(function(resolve, reject) {
resolve('A');
});

p1.then(function(v) {
console.log(v);
});

p2.then(function(v) {
console.log(v);
});

// A B <-- 不是你可能期望的 B A

为了避免这样的问题, 我们不应当依靠任何跨 Promise 的回调顺序. 当然, 最好是在代码中根本不要让多个回调的顺序成为问题.

Promise 模式

  • Promise.all: "与", 多个并发流程都完成才继续执行:
// 这里假定了request(..)是一个兼容Promise的Ajax工具
var p1 = request('http://some.url.1/');
var p2 = request('http://some.url.2/');

Promise.all([p1, p2])
.then(function(msgs) {
// `p1`和`p2`都已完成,这里将它们的消息传入
return request('http://some.url.3/?v=' + msgs.join(','));
})
.then(function(msg) {
console.log(msg);
});
  • Promise.race: "或", 多个并发只要一个完成了, 就继续下一步并且丢弃其他 Promise:
// `foo()`是一个兼容Promise

// `timeoutPromise(..)`
// 返回一个在指定延迟之后会被拒绝的Promise

// 为`foo()`设置一个超时
Promise.race([
foo(), // 尝试`foo()`
timeoutPromise(3000) // 给它3秒钟
]).then(
function() {
// `foo(..)`及时地完成了!
},
function(err) {
// `foo()`要么是被拒绝了,要么就是没有及时完成
// 可以考察`err`来知道是哪一个原因
}
);

注意一点, 如果一个空的 Array 被传入Promise.all(), 它会立即完成, 但Promise.race会永远挂起, 永远不会解析.

如果构建了一个不包含错误处理的 Promise 链, 这个链的任意位置发生的任何错误都将沿着链条向下无限传播, 直到被监听为止.

并发迭代

有时候如果要迭代一个 Promise 的列表, 并对他们所有都实施一些任务, 就像 array 的 forEach 一样, 可以考虑设计一个异步的 map 工具:

if (!Promise.map) {
Promise.map = function(vals, cb) {
// 一个等待所有被映射的promise的新promise
return Promise.all(
// 注意:普通的数组`map(..)`,
// 将值的数组变为promise的数组
vals.map(function(val) {
// 将`val`替换为一个在`val`
// 异步映射完成后才解析的新promise
return new Promise(function(resolve) {
cb(val, resolve);
});
})
);
};
}

不过, 在这个 map 的实现中, 无法表示异步拒绝, 但如果一个在映射的毁掉内部发生一个同步的异常/错误, Promise.map 返回的 Promise 就会拒绝.

并发限制

Promise.all的并发限制指的是, 每个时刻并发执行的promise的数量是固定的, 最终的执行结果还是保持与原来的Promise.all一直.

我们知道promise实际上在实例化对象的时候就执行相应的代码了. 所以我们需要在实例化的时候就去控制相关的逻辑, 把它交给并发控制的逻辑.

我们可以直接参考一些第三方的实现, 比如asycn-pool:

function asyncPool(poolLimit, array, iteratorFn) {
let i = 0;
const ret = [];
const executing = [];
const enqueue = function () {
// 边界处理,array为空数组
if (i === array.length) {
return Promise.resolve();
}
// 每调一次enqueue,初始化一个promise
const item = array[i++];
const p = Promise.resolve().then(() => iteratorFn(item, array));
// 放入promises数组
ret.push(p);
// promise执行完毕,从executing数组中删除
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
// 插入executing数字,表示正在执行的promise
executing.push(e);
// 使用Promise.race,每当executing数组中promise数量低于poolLimit,就实例化新的promise并执行
let r = Promise.resolve();
if (executing.length >= poolLimit) {
r = Promise.race(executing);
}
// 递归,直到遍历完array
return r.then(() => enqueue());
};
return enqueue().then(() => Promise.all(ret));
}

大概的逻辑描述为:

  1. 从array的第一个元素开始, 初始化promise对象, 同时用一个executing数组保存正在执行的Promise
  2. 不断的初始化promise, 直到达到pollLimit
  3. 使用Promise.race, 获取正在执行的executing中promise 的执行情况, 当有一个promise执行完毕, 继续初始化promise并放入到executing中
  4. 所有的promise都执行完毕了, 就调用promise.all返回

Generator

Promise 实际上也是基于回调的异步流程控制. 这种表达会有两个关键缺陷:

  • 基于回调的异步与我们大脑规划任务的各个步骤的过程不相符
  • 由于控制倒转回调时不可靠的, 也是不可组合的.

Promise 可以通过一些额外的代码控制重建可靠性和可组合型. 此外还有一种顺序的, 看起来同步的风格来表达异步流程控制, 就是本节的 Generator:

var x = 1;

//注意定义方式
function* foo() {
x++;
yield; // 暂停!
console.log('x:', x);
}

function bar() {
x++;
}

// 构建一个迭代器`it`来控制generator
var it = foo();

// 在这里开始`foo()`!
it.next();
x; // 2
bar();
x; // 3
it.next(); // x: 3

在抢占式的多线程语言中, 一个线程的函数干扰另一个线程的函数的两个语句中间的状态是可能的, 虽然 JS 既不是抢占式也(还)不是多线程的, 但是这种"干扰"的协作形式确实有可能的.

generator 可以将内部的信息进行输入和输出(yield).

一般来说, 一个迭代器拥有的next()数量会比一个 Generator 中的 yield 语句的数量多一个.

function* foo(x) {
var y = x * (yield);
return y;
}

var it = foo(6);

// 开始`foo(..)`
it.next();

var res = it.next(7);

res.value; // 42

第一个next启动一个generator, 然后运行到第一个yield, 第二个next调用满足了第一个暂停的yield表达式, 而第三个next将满足第二个yield, 如此反复.

第二个next给第一个yield输入参数, 也可以说yield传递一个值出来给next.

多迭代器

每当你构建一个迭代器, 你都隐含的构建了一个将由这个迭代器控制的generator的实例.

同一个generator的多个实例可以同时运行, 甚至可以互动.

function* foo() {
var x = yield 2;
z++;
var y = yield x * z;
console.log(x, y, z);
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value; // 2 <-- 让出2
var val2 = it2.next().value; // 2 <-- 让出2

val1 = it1.next(val2 * 10).value; // 40 <-- x:20, z:2
val2 = it2.next(val1 * 5).value; // 600 <-- x:200, z:3

it1.next(val2 / 2); // y:300
// 20 300 3
it2.next(val1 / 4); // y:10
// 200 10 3

异步迭代 Generator

使用Generator来控制一个ajax任务流程:

function foo(x, y) {
ajax('http://some.url.1/?x=' + x + '&y=' + y, function(err, data) {
if (err) {
// 向`*main()`中扔进一个错误
it.throw(err);
} else {
// 使用收到的`data`来继续`*main()`
it.next(data);
}
});
}

function* main() {
try {
var text = yield foo(11, 31);
console.log(text);
} catch (err) {
console.error(err);
}
}

var it = main();

// 使一切开始运行!
it.next();

看起来我们调用了一个普通的函数foo(), 然而我们却以同步的方式获得了一步的结果, 实际上它仅仅暂停/阻塞了 generator 本身的代码.

实际上, 我们将异步处理作为实现细节抽象出去, 以至于我们可以同步的/顺序的推理我们的流程控制.

甚至于yield还允许generatorcatch一个错误. (try..catch 无法捕获异步的错误).

function* main() {
var x = yield 'Hello World';

yield x.toLowerCase(); // 引发一个异常!
}

var it = main();

it.next().value; // Hello World

try {
it.next(42);
} catch (err) {
console.error(err); // TypeError
}

Generator + Promise

发挥 Promise 和 generator 的最大功效的自然方法是 yield 一个 Promise,并将这个 Promise 连接到 generator 的 迭代器 的控制端。

下面是一个基于 Promise 的 Ajax 的例子:

function foo(x, y) {
return request('http://some.url.1/?x=' + x + '&y=' + y);
}

foo(11, 31).then(
function(text) {
console.log(text);
},
function(err) {
console.error(err);
}
);

将这个 Promise 封装到一个 Generator 函数中:

function foo(x, y) {
return request('http://some.url.1/?x=' + x + '&y=' + y);
}

function* main() {
try {
var text = yield foo(11, 31);
console.log(text);
} catch (err) {
console.error(err);
}
}

在 generator 内部, 无论什么样的值被被yeild出去都是不可见的实现细节, 下面来运行这个 generator:

var it = main();

var p = it.next().value;

// 等待`p` promise解析
p.then(
function(text) {
it.next(text);
},
function(err) {
it.throw(err);
}
);

简单的组合非常好用, 但是仍然存在一些问题:

  1. 我们需要为每一个 generator 手动编写一个不同的 Promise 链.
  2. generator 在it.next()期间抛出一个错误, 我们应当将其 catch 还是直接往上抛出?

带有 Promise 的 Generator 运行期

有一些抽象库提供了这样的工具:

// 感谢Benjamin Gruenbaum (@benjamingr在GitHub)在此做出的巨大改进!
function run(gen) {
var args = [].slice.call(arguments, 1);
var it;

// 在当前的上下文环境中初始化generator
it = gen.apply(this, args);

// 为generator的完成返回一个promise
return Promise.resolve().then(function handleNext(value) {
// 运行至下一个让出的值
var next = it.next(value);

return (function handleResult(next) {
// generator已经完成运行了?
if (next.done) {
return next.value;
}
// 否则继续执行
else {
return Promise.resolve(next.value).then(
// 在成功的情况下继续异步循环,将解析的值送回generator
handleNext,

// 如果`value`是一个拒绝的promise,就将错误传播回generator自己的错误处理g
function handleErr(err) {
return Promise.resolve(it.throw(err)).then(handleResult);
}
);
}
})(next);
});
}

按照这个函数, 我们可以自动的异步的推进 generator 函数, 直到整个函数完成.

function* main() {
// ..
}

run(main);

Generator 中的 Promise 并发

function* foo() {
// 使两个请求“并行”并等待两个promise都被解析
var results = yield Promise.all([request('http://some.url.1'), request('http://some.url.2')]);

var r1 = results[0];
var r2 = results[1];

var r3 = yield request('http://some.url.3/?v=' + r1 + ',' + r2);

console.log(r3);
}

// 使用前面定义的`run(..)`工具
run(foo);

作为一种代码分割, 我们应当尽量使得 generator 内部的代码相对简单, 顺序, 并且看起来同步, 将尽可能多的异步细节隐藏在这些代码之外. 比如下面这种写法:

// 注意:这是一个普通函数,不是generator
function bar(url1, url2) {
return Promise.all([request(url1), request(url2)]);
}

function* foo() {
// 将基于Promise的并发细节隐藏在`bar(..)`内部
var results = yield bar('http://some.url.1', 'http://some.url.2');

var r1 = results[0];
var r2 = results[1];

var r3 = yield request('http://some.url.3/?v=' + r1 + ',' + r2);

console.log(r3);
}

// 使用刚才定义的`run(..)`工具
run(foo);

将异步性, 像 Promise 这种, 作为一种实现细节也许是更好的实践方法.

注意: 对于编程来说, 抽象不总是一种好的东西, 它在带来简洁性的同时会增加复杂度, 但是这种情况下, 我们认为这样的风格是更健康的.

Generator 委托

Generator 委托, 指的是从一个 generator 调用另一个 generator 的写法:

function* foo() {
var r2 = yield request('http://some.url.2');
var r3 = yield request('http://some.url.3/?v=' + r2);

return r3;
}

function* bar() {
var r1 = yield request('http://some.url.1');

// 通过`run(..)`“委托”到`*foo()`
var r3 = yield run(foo);

console.log(r3);
}

run(bar);

除了 run 委托, yield 是一种更好的办法: yield * __:

function* foo() {
console.log('`*foo()` starting');
yield 3;
yield 4;
console.log('`*foo()` finished');
}

function* bar() {
yield 1;
yield 2;
yield* foo(); // `yield`-delegation!
yield 5;
}

var it = bar();

it.next().value; // 1
it.next().value; // 2
it.next().value; // `*foo()` starting
// 3
it.next().value; // 4
it.next().value; // `*foo()` finished
// 5

我们在调用foo()的时候创建了一个迭代器, 然后yield *将迭代器的控制传递给了另一个foo迭代器.

委托的语法提供了更灵活的generator使用方式, 能有效增强代码的可读性, 可维护和可调试性.

function* foo() {
var r2 = yield request('http://some.url.2');
var r3 = yield request('http://some.url.3/?v=' + r2);

return r3;
}

function* bar() {
var r1 = yield request('http://some.url.1');

// 通过`run(..)`“委托”到`*foo()`
var r3 = yield* foo();

console.log(r3);
}

run(bar);

Generator 双向消息传递

function* foo() {
console.log('inside `*foo()`:', yield 'B');

console.log('inside `*foo()`:', yield 'C');

return 'D';
}

function* bar() {
console.log('inside `*bar()`:', yield 'A');

// `yield`-委托!
console.log('inside `*bar()`:', yield* foo());

console.log('inside `*bar()`:', yield 'E');

return 'F';
}

var it = bar();

console.log('outside:', it.next().value);
// outside: A

console.log('outside:', it.next(1).value);
// inside `*bar()`: 1
// outside: B

console.log('outside:', it.next(2).value);
// inside `*foo()`: 2
// outside: C

console.log('outside:', it.next(3).value);
// inside `*foo()`: 3
// inside `*bar()`: D
// outside: E

console.log('outside:', it.next(4).value);
// inside `*bar()`: 4
// outside: F

yield甚至不需要委托一个generator, 也可以委托一个iterable:

function* bar() {
console.log('inside `*bar()`:', yield 'A');

// `yield`-委托至一个非generator
console.log('inside `*bar()`:', yield* ['B', 'C', 'D']);

console.log('inside `*bar()`:', yield 'E');

return 'F';
}

var it = bar();

console.log('outside:', it.next().value);
// outside: A

console.log('outside:', it.next(1).value);
// inside `*bar()`: 1
// outside: B

console.log('outside:', it.next(2).value);
// outside: C

console.log('outside:', it.next(3).value);
// outside: D

console.log('outside:', it.next(4).value);
// inside `*bar()`: undefined
// outside: E

console.log('outside:', it.next(5).value);
// inside `*bar()`: 5
// outside: F

不同的是, array 迭代器不关心任何通过next()传递进去的消息, 所以 2,3,4 实际上被忽略了, 此外,这个迭代器没有明确的return值, 所以yield *在表达式完成时得到了一个undefined.

yield委托在两个方向上透明的传递消息的方式相同, 错误和异常也会进行双向传递.

异步委托

function* foo() {
var r2 = yield request('http://some.url.2');
var r3 = yield request('http://some.url.3/?v=' + r2);

return r3;
}

function* bar() {
var r1 = yield request('http://some.url.1');

var r3 = yield* foo();

console.log(r3);
}

run(bar);

递归委托

yield委托可以一直持续委托下去, 我们甚至可以再具有一部能力的 generator 上”递归”使用yield委托:

function* foo(val) {
if (val > 1) {
// 递归委托
val = yield* foo(val - 1);
}

return yield request('http://some.url/?v=' + val);
}

function* bar() {
var r1 = yield* foo(3);
console.log(r1);
}

run(bar);

Generator 并发

// `request(..)` 是一个基于Promise的Ajax工具

var res = [];

function* reqData(url) {
res.push(yield request(url));
}

var it1 = reqData('http://some.url.1');
var it2 = reqData('http://some.url.2');

var p1 = it1.next().value;
var p2 = it2.next().value;

p1.then(function(data) {
it1.next(data);
return p2;
}).then(function(data) {
it2.next(data);
});

另一种并发:

// `request(..)` 是一个基于Promise的Ajax工具

var res = [];

function* reqData(url) {
var data = yield request(url);

// 传递控制权
yield;

res.push(data);
}

var it1 = reqData('http://some.url.1');
var it2 = reqData('http://some.url.2');

var p1 = it1.next().value;
var p2 = it2.next().value;

p1.then(function(data) {
it1.next(data);
});

p2.then(function(data) {
it2.next(data);
});

Promise.all([p1, p2]).then(function() {
it1.next();
it2.next();
});

Async/Await

Async/Await 就是一个自执行的 generate 函数。利用 generate 函数的特性把异步的代码写成“同步”的形式。

const fs = require('fs');

const readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};

const gen = function*() {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。

这里有个例子:

async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');

/**
* script start
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* setTimeout
*/

其中在执行 async1 的时候, 遇到了 await,会将 await 后面的表达式执行一遍,所以就紧接着输出 async2,然后将 await 后面的代码也就是 console.log('async1 end')加入到 microtask 中的 Promise 队列中,接着跳出 async1 函数来执行后面的代码。

应用

1. 实现一个 sleep 函数

//Promise
const sleep = time => {
return new Promise(resolve => setTimeout(resolve, time));
};
sleep(1000).then(() => {
console.log(1);
});

//Generator
function* sleepGenerator(time) {
yield new Promise(function(resolve, reject) {
setTimeout(resolve, time);
});
}
sleepGenerator(1000)
.next()
.value.then(() => {
console.log(1);
});

//async
function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function output() {
let out = await sleep(1000);
console.log(1);
return out;
}
output();

//ES5
function sleep(callback, time) {
if (typeof callback === 'function') setTimeout(callback, time);
}

function output() {
console.log(1);
}
sleep(output, 1000);

其他

实现一个 Promise

首先我们实现一个最简单的promise函数:

// 三个状态:PENDING、FULFILLED、REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
constructor(executor) {
// 默认状态为 PENDING
this.status = PENDING;
// 存放成功状态的值,默认为 undefined
this.value = undefined;
// 存放失败状态的值,默认为 undefined
this.reason = undefined;

// 调用此方法就是成功
let resolve = (value) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}

// 调用此方法就是失败
let reject = (reason) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
}

try {
// 立即执行,将 resolve 和 reject 函数传给使用者
executor(resolve,reject)
} catch (error) {
// 发生异常时执行失败逻辑
reject(error)
}
}

// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}

if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}

这是一个基础班的proise, 但是我们只处理了同步操作, 而没有处理异步操作. 进行适当的改造:

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 存放成功的回调
this.onResolvedCallbacks = [];
// 存放失败的回调
this.onRejectedCallbacks= [];

let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 依次将对应的函数执行
this.onResolvedCallbacks.forEach(fn=>fn());
}
}

let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 依次将对应的函数执行
this.onRejectedCallbacks.forEach(fn=>fn());
}
}

try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}

then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}

if (this.status === REJECTED) {
onRejected(this.reason)
}

if (this.status === PENDING) {
// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
});

// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}

我们用一个发布订阅模式, 实现了: 收集依赖 => 触发通知 => 取出依赖执行. 成功的支持了内部的异步执行.

除此之外, 我们知道promise的优势在于链式调用, 在我们使用Promise的时候, 当then函数中return了一个值, 我们就能在下一个then中获取到, 这就是所谓的then的链式调用. 而且, 当then()为空的时候, 后面的then依然可以获取前面的值. 即所谓的值的穿透

那具体如何实现的呢? 本质上就是在调用then的时候, 重新创建一个promise对象, 把上一个then的返回结果传递给这个新的promisethen方法.

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

/**
*
* @param {*} promise2 准备被返回的promise对象
* @param {*} x then函数中的第一个参数执行的结果
* @param {*} resolve 返回的promise的resolve方法
* @param {*} reject 返回的promise的reject方法
*/
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成是错误的实现, 用一个类型错误, 结束掉promise
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>'),
);
}

// 只能调用一次
let called;
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
// 限制了x只能是一个function或者对象
try {
let then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
// 递归解析的过程
resolvePromise(promise2, y, resolve, reject);
},
r => {
// 只要失败了, 就是啊比
if (called) return;
called = true;
reject(r);
},
);
} else {
// 如果 x.then 是一个普通值, 就直接返回resolve作为结果
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(e);
}
} else {
// 如果x是个普通值, 就直接返回resolve作为结果
resolve(x);
}
};

// 收集依赖 => 触发通知 => 取出依赖执行
class MyPromise {
constructor(executor) {
// 当前的工作状态
this.status = PENDING;
// 存放成功结果的值
this.value = undefined;
// 存放失败结果的值
this.reason = undefined;
// 存放成功的回调
this.onResolvedCallbacks = [];
// 存放失败的回调
this.onRejectedCallbacks = [];

let resolve = value => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 依次将对应的函数执行
this.onResolvedCallbacks.forEach(fn => fn());
}
};

let reject = reason => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 依次将对应的函数执行
this.onRejectedCallbacks.forEach(fn => fn());
}
};

try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}

then(onFulfilled, onRejected) {
// 解决onFulfilled, onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
// 因为错误的值要让后面访问到, 所以这里也要抛出一个错误, 否则会在之后的then的resolve中被捕获
onRejected = typeof onRejected === 'function' ? onRejected : v => v;

// 每次调用then都要返回一个新的promise
let promise2 = new MyPromise((resolve, reject) => {
// 这部分代码是直接执行的, 用setTimeout实现异步
// promise 已完成的情况
if (this.status === FULFILLED) {
setTimeout(() => {
try {
// 获取onFulfilled得到的值
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}

if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}

if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});

this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}

const promise = new MyPromise((resolve, reject) => {
reject('失败');
}).then().then(
data => {
console.log(data);
},
err => {
console.log('err', err);
},
);

这里返回promise的逻辑也好理解, 不过其中的resolvePromise函数的作用是什么呢?

let then = x.then;
// 这里表示有then方法, 则x应该是一个promise
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
// 递归解析的过程
resolvePromise(promise2, y, resolve, reject);
},
r => {
// 只要失败了, 就是啊比
if (called) return;
called = true;
reject(r);
},
);
} else {
// 如果 x.then 是一个普通值, 就直接返回resolve作为结果
resolve(x);
}

x是我们执行resolve后获取的值, 如果x上有then且该then可以调用, 我们就认为x就是一个promise, 所以在这里对x进行调用, 获取到xthen中的值, 并且进行递归, 直到获取到一个普通的非promise值为止

因为我们需要把x作为值传递给下一个then方法.

Promise的主题内容代码就完成了, 接下去补充一些API:

  • promise.resolve: 默认产生一个成功的promise:
static resolve(data){
return new Promise((resolve,reject)=>{
resolve(data);
})
}
  • promise.reject:
static reject(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
  • promise.finally:
Promise.prototype.finally = function(callback) {
return this.then((value)=>{
return Promise.resolve(callback()).then(()=>value)
},(reason)=>{
return Promise.resolve(callback()).then(()=>{throw reason})
})
}
  • promise.all:
Promise.all = function(values) {
if (!Array.isArray(values)) {
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}
return new Promise((resolve, reject) => {
let resultArr = [];
let orderIndex = 0;
const processResultByKey = (value, index) => {
resultArr[index] = value;
if (++orderIndex === values.length) {
resolve(resultArr)
}
}
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value && typeof value.then === 'function') {
value.then((value) => {
processResultByKey(value, i);
}, reject);
} else {
processResultByKey(value, i);
}
}
});
}
  • promise.race:
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
// 一起执行就是for循环
for (let i = 0; i < promises.length; i++) {
let val = promises[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reject);
} else { // 普通值
resolve(val)
}
}
});
}

参考链接