异步

时间:2021-6-12 作者:qvyue

JS 异步解决方案的发展历程以及优缺点。

  • 1、回调函数(callback)
    优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队
    等着,会拖延整个程序的执行。)
    缺点:回调地狱,不能用 try catch 捕获错误,不能 return
  • 2、Promise
    优点:解决了回调地狱的问题
    缺点:无法取消 Promise(return 一个pendding状态的promise即可结果不向后传递) ,错误需要通过回调函数来捕获
  • 3、Generator
    特点:可以控制函数的执行,可以配合 co 函数库使用
  • 4、Async/await
    优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使
    用 await 会导致性能上的降低。
  1. 手写promise相关代码
// Promise 三种状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED"; // resolve
const REJECTED = "REJECTED"; // reject

const resolvePromise = (promise2, x, resolve, reject) => {
    // 处理x的类型来决定下次then的状态是resolve还是reject
    // promise2 === x的情况就相当于
    // new Promise(resolve => resolve(1)).then(res => a1);
    if (promise2 === x) {
        return reject(
            new TypeError(`Chaining cycle detected for promise #`)
        );
    }
    let called = false;
    // 判断x是不是一个普通函数
    if ((typeof x === "object" && x !== null) || typeof x === "function") {
        // 判断是否有then方法来判断是不是promise
        try {
            let then = x.then;
            if (typeof then === "function") {
                // 是promise情况 使用then.call来执行x.then方法是为了避免有的对象写的只能获取一次。
                // x.then需要再获取一次 而then.call是上次的缓存
                then.call(
                    x,
                    (y) => {
                        // 参数可能还是promise需要递归
                        // .then(res => new Promise(resolve => resolve(new Promise..)))
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    (r) => {
                        if (called) return; // 防止多次调用
                        called = true;
                        reject(r);
                    }
                );
            } else {
                // [1,2,3] {a:1}
                resolve(x);
            }
        } catch (err) {
            if (called) return; // 防止多次调用
            called = true;
            reject(err);
        }
    } else {
        // 不是对象或者函数 普通值
        resolve(x);
    }
};

class Promise {
    constructor(executor) {
        this.value = undefined;
        this.reason = undefined;
        // 保存promise状态
        this.status = PENDING;

        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            // 如果resolve或者reject一个promise
            if (value instanceof Promise) {
                return value.then(resolve, reject);
            }
            //规定: 如果一旦状态改变为成功或失败,就不能再变
            if (this.status === PENDING) {
                this.value = value;
                this.status = FULFILLED;
                this.onResolvedCallbacks.forEach((fn) => fn());
            }
        };
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach((fn) => fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            // 使用throw new Error也可以进入rejected逻辑
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // // 如果成功回调不是函数则将value继续向下传递,失败则将reason继续向后抛
        onFulfilled =
            typeof onFulfilled === "function" ? onFulfilled : (val) => val;
        onRejected =
            typeof onRejected === "function"
                ? onRejected
                : (err) => {
                    throw err;
                };

        // 返回一个新的promise。实现链式调用
        // 因为then中的回调函数是异步执行的。为了确保newPromise存在,需要setTimeout
        let newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                if (this.status === FULFILLED) {
                    // 如果执行then中报错,需要直接reject
                    try {
                        // 执行下then中的方法的返回值当成下一个then的参数传递
                        let x = onFulfilled(this.value);
                        // 返回值有多种情况。普通值或者还是一个promise
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch (err) {
                        reject(err);
                    }
                }

                if (this.status === REJECTED) {
                    // 如果失败的then中有报错或者还返回一个promise直接向后传递即可
                    let x = onRejected(this.reason);
                    reject(x);
                }

                if (this.status === PENDING) {
                    this.onResolvedCallbacks.push(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(newPromise, x, resolve, reject);
                        } catch (err) {
                            reject(err);
                        }
                    });
                    this.onRejectedCallbacks.push(() => {
                        let x = onRejected(this.reason);
                        reject(x);
                    });
                }
            });
        });
        return newPromise;
    }
}

/*
1. Promise.any vs Promise.all

  Promise.any() 和 Promise.all() 从返回结果来看,它们 彼此相反 :

  Promise.all() :任意一个 promise 被 reject ,就会立即被 reject ,并且 reject 的是第一个抛出的错误信息,只有所有的 promise 都 resolve 时才会 resolve 所有的结果
Promise.any() :任意一个 promise 被 resolve ,就会立即被 resolve ,并且 resolve 的是第一个正确结果,只有所有的 promise 都 reject 时才会 reject 所有的失败信息
另外,它们又有不同的 重点 :

  Promise.all() 对所有实现都感兴趣。相反的情况(至少一个拒绝)导致拒绝。
  Promise.any() 对第一个实现感兴趣。相反的情况(所有拒绝)导致拒绝。


2. Promise.any vs Promise.race

  Promise.any() 和 Promise.race() 的 关注点 不一样:

  Promise.any() :关注于 Promise 是否已经解决
  Promise.race() :主要关注 Promise 是否已经解决,无论它是被解决还是被拒绝
*/




// 手写   使用了Promise.resolve(promise) 包裹promise,可能用户输入的是非promise,比如222, thenable:      {then: ...}
// Promise.all;

Promise.all = function(promises) {
    let successNum = 0;
    let promisesLength = promises.length;
    let arr = Array(promisesLength).fill(); //[undefined]
    return new Promise((resolve, reject) => {
        for (let index = 0; index  {
                  arr[index] = res;
                  if(++successNum === promisesLength) resolve(arr)
            }, (err) => {
                  reject(err)
            })
        }
    })
}

// 从最快的服务器检索资源
// 来自世界各地的用户访问网站,如果你有多台服务器,则尽量使用响应速度最快的服务器,
// 在这种情况下,可以使用 Promise.any() 方法从最快的服务器接收响应
Promise.any = function (promises) {
  return new Promise((resolve, reject) => {
    promises = Array.isArray(promises) ? promises : [];
    let len = promises.length;
    // 用于收集所有 reject
    let errs = [];
    // 如果传入的是一个空数组,那么就直接返回 AggregateError
    if (len === 0)
      return reject(new AggregateError("All promises were rejected"));
    promises.forEach((promise) => {
      Promise.resolve(promise).then(
        (value) => {
          resolve(value);
        },
        (err) => {
          len--;
          errs.push(err);
          if (len === 0) {
            reject(new AggregateError(errs));
          }
        }
      );
    });
  });
};


Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        for (let index = 0; index  {
                   resolve(res)
             }, (err) => {
                   reject(err)
             })
        }
    })
}

// promise.race可以很多改变状态的操作,比如取消promise。
// 或者像如下 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected
const p = Promise.race([
    fetch('/resource-that-may-take-a-while'),
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);


Promise.prototype.finally = function(fn) {
    let P = this.constructor;
    return this.then(
        value  => P.resolve(fn()).then(() => value),
        reason => P.resolve(fn()).then(() => { throw reason })
    );
}


// 实现一个sleep函数
function sleep(time) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, time);
    })
}

2.有8个图片资源的url,已经存储在数组urls中,且已有一个函数function loading,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject

var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://www.kkkk1000.com/images/wxQrCode2.png'];

// 异步加载图片,预加载能获取图片相关属性和数据
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = () => {
            console.log('一张图片加载完成');
            resolve();
        }
        img.onerror = reject;
        img.src = url;
    })
};

function limitLoad(urls, handler, limit) {
    // 对数组做一个拷贝
    const sequence = [...urls];

    let promises = [];//并发请求到最大数
    promises = sequence.splice(0, limit).map((url, index) => {
        // 这里返回的 index 是任务在 promises 的脚标,用于在 Promise.race 之后找到完成的任务脚标
        return handler(url).then(() => {
            return index;
        });
    });

    // 利用数组的 reduce 方法来以队列的形式执行
    return sequence.reduce((last, url, currentIndex) => {
        return last.then(() => {
            // 返回最快改变状态的 Promise
            return Promise.race(promises)
        }).catch(err => {
            // 这里的 catch 不仅用来捕获前面 then 方法抛出的错误
            // 更重要的是防止中断整个链式调用
            console.error(err)
        }).then((res) => {
            // 用新的 Promise 替换掉最快改变状态的 Promise
            promises[res] = handler(sequence[currentIndex]).then(() => {
                return res
            });
        })
    }, Promise.resolve()).then(() => {
        return Promise.all(promises)
    })

}

limitLoad(urls, loadImg, 3);
  1. eventLoop(浏览器 & node)
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。